Initial commit: 让多个 AI 智能体通过文件总线在共享聊天室里互相 @、协作与交接任务

This commit is contained in:
WpyQwq
2026-09-19 11:52:42 +08:00
commit 103f0b32f5
28 changed files with 5834 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env node
/**
* api-call.mjs — 把任意 OpenAI 兼容的 chat/completions 端点,包装成一个"无头 CLI"。
*
* 为什么要有它:群聊里每个成员都是"一个能被 spawn 的命令"。有的成员没有 CLI(例如直接用
* 智谱 BigModel API 的 GLM-4-Flash),那就给它一个只做一件事的小包装:发一次请求、把回答打到 stdout。
*
* 用法(提示词必须是**最后一个**位置参数,aig 会把提示词追加在最后):
* node api-call.mjs --base https://open.bigmodel.cn/api/paas/v4 --model GLM-4-Flash-250414 \
* --key-file ~/.zcode/v2/provider_config.json --key-path config.a.b.0.key "你的提示词"
*
* 密钥三种来源(按优先级):--key-env <VAR> / --key-file + --key-path / --key <明文>
* 输出:一行 JSON {type:"result", result:"…", model, usage},方便 aig 结构化取答复。
*/
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
const argv = process.argv.slice(2);
const flag = (n, d) => { const i = argv.indexOf('--' + n); return i > -1 && argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[i + 1] : d; };
const BASE = String(flag('base', '')).replace(/\/+$/, '');
const MODEL = String(flag('model', ''));
const TIMEOUT = Number(flag('timeout', 180)) * 1000;
const SYSTEM = flag('system', '');
const prompt = argv[argv.length - 1] && !argv[argv.length - 1].startsWith('--') ? argv[argv.length - 1] : '';
const posArgs = argv.filter((a) => !a.startsWith('--'));
if (!BASE || !MODEL || !prompt) {
process.stderr.write('api-call: 需要 --base、--model 和非空提示词\n');
process.exit(2);
}
function resolveKey() {
const envName = flag('key-env', '');
if (envName) return process.env[envName] || '';
const file = flag('key-file', '');
if (file) {
const p = file.replace(/^~(?=\/|\\)/, os.homedir());
let obj;
try { obj = JSON.parse(fs.readFileSync(p, 'utf8')); } catch (e) { throw new Error(`读不到密钥文件 ${p}:${e.message}`); }
const dotted = String(flag('key-path', ''));
if (dotted) {
let cur = obj;
for (const seg of dotted.split('.')) {
if (cur == null) break;
cur = Array.isArray(cur) ? cur[Number(seg)] : cur[seg];
}
if (typeof cur === 'string' && cur) return cur;
throw new Error(`key-path "${dotted}" 在 ${p} 里没找到字符串`);
}
// 没给路径就宽容地找第一个像 key 的字段
const found = (function walk(o, depth) {
if (depth > 6 || o == null) return null;
if (typeof o === 'string') return null;
for (const [k, v] of Object.entries(o)) {
if (/^(api[_-]?key|apikey|token)$/i.test(k) && typeof v === 'string' && v.length > 8) return v;
const deep = walk(v, depth + 1);
if (deep) return deep;
}
return null;
})(obj, 0);
if (found) return found;
throw new Error(`没在 ${p} 里找到 api key`);
}
return String(flag('key', ''));
}
let key;
try { key = resolveKey(); } catch (e) { process.stderr.write(`api-call: ${e.message}\n`); process.exit(3); }
if (!key) { process.stderr.write('api-call: 没拿到密钥(--key-env / --key-file / --key 都没给有效的)\n'); process.exit(3); }
const messages = [];
if (SYSTEM) messages.push({ role: 'system', content: SYSTEM });
messages.push({ role: 'user', content: prompt });
const ctl = new AbortController();
const timer = setTimeout(() => ctl.abort(), TIMEOUT);
try {
const res = await fetch(`${BASE}/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json', authorization: `Bearer ${key}` },
body: JSON.stringify({ model: MODEL, messages, stream: false }),
signal: ctl.signal,
});
const text = await res.text();
clearTimeout(timer);
if (!res.ok) {
process.stderr.write(`api-call: HTTP ${res.status} ${text.slice(0, 400)}\n`);
process.exit(4);
}
let json;
try { json = JSON.parse(text); } catch { process.stderr.write(`api-call: 返回不是 JSON:${text.slice(0, 300)}\n`); process.exit(5); }
const reply = json.choices?.[0]?.message?.content;
if (typeof reply !== 'string') {
process.stderr.write(`api-call: 拿不到 choices[0].message.content:${text.slice(0, 300)}\n`);
process.exit(6);
}
process.stdout.write(`${JSON.stringify({
type: 'result', subtype: 'success', model: json.model || MODEL,
result: reply.trim(), usage: json.usage || null,
finish_reason: json.choices?.[0]?.finish_reason || null,
})}\n`);
} catch (e) {
clearTimeout(timer);
process.stderr.write(`api-call: ${e.name === 'AbortError' ? '请求超时' : e.message}\n`);
process.exit(7);
}