@bolloon/bolloon-agent 0.3.8 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/pi-sdk-tools.js +90 -9
- package/dist/constraint-runtime/src/tools/PolymarketSDK/cancelOrder.js +19 -4
- package/dist/constraint-runtime/src/tools/PolymarketSDK/clobShared.js +104 -0
- package/dist/constraint-runtime/src/tools/PolymarketSDK/createOrder.js +34 -4
- package/dist/constraint-runtime/src/tools/PolymarketSDK/getOrders.js +17 -4
- package/dist/external-engines/delegate.js +148 -0
- package/dist/external-engines/discovery.js +394 -0
- package/dist/external-engines/index.js +9 -0
- package/dist/external-engines/types.js +11 -0
- package/dist/web/api-config.html +250 -1
- package/dist/web/routes-external-engines.js +111 -0
- package/dist/web/server.js +4 -0
- package/dist/web/style.css +31 -0
- package/package.json +1 -1
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* external-engines/discovery.ts — 自动发现本机已安装的外部编码智能体
|
|
3
|
+
*
|
|
4
|
+
* 设计原则:
|
|
5
|
+
* - 纯函数 + 可注入 deps, 便于单测 (不真实碰 fs / 不真实 spawn)
|
|
6
|
+
* - best-effort: 不同工具版本配置文件 schema 会变, 用一个宽松的 key 提取器
|
|
7
|
+
* - 发现 ≠ 启用: 这里只回报"装了没 / 配了没", 真正写进 provider 由 import 负责
|
|
8
|
+
*
|
|
9
|
+
* 复用现有范式: 参照 src/pi-ecosystem-mcp/index.ts 的 discoverMcpServers()
|
|
10
|
+
*/
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import * as fs from 'fs/promises';
|
|
13
|
+
import { spawn } from 'child_process';
|
|
14
|
+
/**
|
|
15
|
+
* 可筛选的模型候选列表.
|
|
16
|
+
* OpenCode / OpenClaw / Hermes 是 provider 无关 (openai 兼容 + anthropic 等),
|
|
17
|
+
* 给一份跨供应商的宽列表便于在 UI 里筛选; Codex / Claude Code 给各自供应商的列表.
|
|
18
|
+
* 实验 API 由声明文件决定, 不预置.
|
|
19
|
+
*/
|
|
20
|
+
const OPENAI_COMPAT_MODELS = [
|
|
21
|
+
'gpt-5.5', 'gpt-5', 'gpt-4.1', 'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-3.5-turbo',
|
|
22
|
+
'deepseek-v4-flash', 'deepseek-v4-pro',
|
|
23
|
+
'qwen-plus', 'qwen-max', 'qwen-turbo',
|
|
24
|
+
'moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k',
|
|
25
|
+
'glm-4-flash', 'glm-4', 'glm-4-plus',
|
|
26
|
+
'mimo-v2.5-pro', 'mimo-v2-pro',
|
|
27
|
+
];
|
|
28
|
+
const ANTHROPIC_MODELS = [
|
|
29
|
+
'claude-sonnet-4-5-20250929', 'claude-opus-4', 'claude-3-5-sonnet-20241022',
|
|
30
|
+
'claude-3-5-haiku-20241022', 'claude-3-opus-20240229',
|
|
31
|
+
];
|
|
32
|
+
const GEMINI_MODELS = ['gemini-3.5-flash', 'gemini-2.5-pro', 'gemini-3.1-flash-lite', 'gemini-flash-latest'];
|
|
33
|
+
const OPENROUTER_MODELS = [
|
|
34
|
+
'anthropic/claude-sonnet-4.5', 'anthropic/claude-3.5-sonnet', 'openai/gpt-4.1',
|
|
35
|
+
'deepseek/deepseek-v4-flash', 'google/gemini-2.5-pro',
|
|
36
|
+
];
|
|
37
|
+
/** OpenCode 是 provider 无关, 合并主流供应商列表 */
|
|
38
|
+
const OPENCODE_MODELS = [
|
|
39
|
+
...OPENAI_COMPAT_MODELS,
|
|
40
|
+
...ANTHROPIC_MODELS,
|
|
41
|
+
...GEMINI_MODELS,
|
|
42
|
+
...OPENROUTER_MODELS,
|
|
43
|
+
];
|
|
44
|
+
/** provider 别名 → Bolloon ModelProvider */
|
|
45
|
+
const PROVIDER_ALIASES = {
|
|
46
|
+
openai: 'openai',
|
|
47
|
+
'openai-compatible': 'openai',
|
|
48
|
+
azure: 'openai',
|
|
49
|
+
anthropic: 'anthropic',
|
|
50
|
+
claude: 'anthropic',
|
|
51
|
+
ollama: 'ollama',
|
|
52
|
+
openrouter: 'openrouter',
|
|
53
|
+
gemini: 'gemini',
|
|
54
|
+
google: 'gemini',
|
|
55
|
+
minimax: 'minimax',
|
|
56
|
+
deepseek: 'deepseek',
|
|
57
|
+
kimi: 'kimi',
|
|
58
|
+
moonshot: 'kimi',
|
|
59
|
+
glm: 'glm',
|
|
60
|
+
zhipu: 'glm',
|
|
61
|
+
qwen: 'qwen',
|
|
62
|
+
dashscope: 'qwen',
|
|
63
|
+
mimo: 'mimo',
|
|
64
|
+
xiaomi: 'mimo',
|
|
65
|
+
local: 'local',
|
|
66
|
+
};
|
|
67
|
+
/** 已知引擎规格 (不含 experiment, experiment 由目录扫描动态产出) */
|
|
68
|
+
const KNOWN_ENGINES = [
|
|
69
|
+
{
|
|
70
|
+
id: 'codex',
|
|
71
|
+
displayName: 'OpenAI Codex CLI',
|
|
72
|
+
binaries: ['codex'],
|
|
73
|
+
configFiles: ['.codex/config.json', '.codex/auth.json'],
|
|
74
|
+
envKeys: ['OPENAI_API_KEY', 'CODEX_API_KEY'],
|
|
75
|
+
providerHint: 'openai',
|
|
76
|
+
baseUrlHint: 'https://api.openai.com/v1',
|
|
77
|
+
modelHint: 'gpt-4.1',
|
|
78
|
+
models: OPENAI_COMPAT_MODELS,
|
|
79
|
+
modelFlag: '-m',
|
|
80
|
+
delegateArgs: (p) => ['exec', '--full-auto', p],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'claude-code',
|
|
84
|
+
displayName: 'Claude Code (Anthropic)',
|
|
85
|
+
binaries: ['claude', 'claude-code'],
|
|
86
|
+
configFiles: ['.claude.json', '.config/claude/config.json'],
|
|
87
|
+
envKeys: ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY'],
|
|
88
|
+
providerHint: 'anthropic',
|
|
89
|
+
baseUrlHint: 'https://api.anthropic.com/v1',
|
|
90
|
+
modelHint: 'claude-sonnet-4-5-20250929',
|
|
91
|
+
models: ANTHROPIC_MODELS,
|
|
92
|
+
modelFlag: '--model',
|
|
93
|
+
delegateArgs: (p) => ['-p', p, '--print'],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'opencode',
|
|
97
|
+
displayName: 'OpenCode',
|
|
98
|
+
binaries: ['opencode'],
|
|
99
|
+
configFiles: ['.config/opencode/opencode.json', '.opencode/opencode.json', 'opencode.json'],
|
|
100
|
+
envKeys: ['OPENCODE_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'OPENROUTER_API_KEY'],
|
|
101
|
+
providerHint: 'openai',
|
|
102
|
+
models: OPENCODE_MODELS,
|
|
103
|
+
modelFlag: '-m',
|
|
104
|
+
// opencode run 默认进 TUI 不退出, --format json 强制 headless 输出并退出 (非交互委派必需)
|
|
105
|
+
delegateArgs: (p) => ['run', p, '--format', 'json'],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'openclaw',
|
|
109
|
+
displayName: 'OpenClaw',
|
|
110
|
+
binaries: ['openclaw', 'open-claw'],
|
|
111
|
+
configFiles: ['.openclaw/config.json', '.config/openclaw/config.json'],
|
|
112
|
+
envKeys: ['OPENCLAW_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY'],
|
|
113
|
+
providerHint: 'openai',
|
|
114
|
+
models: OPENCODE_MODELS,
|
|
115
|
+
delegateArgs: (p) => ['run', p],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 'hermes',
|
|
119
|
+
displayName: 'Hermes',
|
|
120
|
+
binaries: ['hermes'],
|
|
121
|
+
configFiles: ['.hermes/config.json', '.config/hermes/config.json'],
|
|
122
|
+
envKeys: ['HERMES_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY'],
|
|
123
|
+
providerHint: 'openai',
|
|
124
|
+
models: OPENCODE_MODELS,
|
|
125
|
+
delegateArgs: (p) => ['prompt', p],
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
// ====================== 默认 deps (真实 IO) ======================
|
|
129
|
+
function realWhichImpl(name) {
|
|
130
|
+
// 用 command -v 解析 PATH; JSON.stringify 防止名字里的元字符注入内层 shell
|
|
131
|
+
return new Promise((resolve) => {
|
|
132
|
+
const p = spawn('sh', ['-c', `command -v ${JSON.stringify(name)}`], {
|
|
133
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
134
|
+
});
|
|
135
|
+
let out = '';
|
|
136
|
+
p.stdout?.on('data', (d) => (out += d.toString()));
|
|
137
|
+
p.on('close', () => resolve(out.trim() || undefined));
|
|
138
|
+
p.on('error', () => resolve(undefined));
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function realReadFileImpl(p) {
|
|
142
|
+
return fs.readFile(p, 'utf-8').then((c) => c).catch(() => undefined);
|
|
143
|
+
}
|
|
144
|
+
function realReaddirImpl(dir) {
|
|
145
|
+
return fs.readdir(dir).then((c) => c).catch(() => undefined);
|
|
146
|
+
}
|
|
147
|
+
export function defaultDeps() {
|
|
148
|
+
return {
|
|
149
|
+
which: realWhichImpl,
|
|
150
|
+
readFile: realReadFileImpl,
|
|
151
|
+
readdir: realReaddirImpl,
|
|
152
|
+
env: process.env,
|
|
153
|
+
home: process.env.HOME || process.env.USERPROFILE || '/tmp',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// ====================== 纯工具函数 (可单测) ======================
|
|
157
|
+
const APIKEY_KEYS = ['apiKey', 'api_key', 'apikey', 'key', 'token', 'access_token', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'OPENROUTER_API_KEY', 'OPENCLAW_API_KEY', 'HERMES_API_KEY', 'CODEX_API_KEY', 'OPENCODE_API_KEY'];
|
|
158
|
+
const BASEURL_KEYS = ['baseUrl', 'base_url', 'apiBase', 'api_base', 'endpoint', 'baseURL', 'API_BASE'];
|
|
159
|
+
const MODEL_KEYS = ['model', 'modelName', 'model_name'];
|
|
160
|
+
const PROVIDER_KEYS = ['provider', 'providerName', 'provider_name'];
|
|
161
|
+
/** 宽松提取: 先看顶层, 再看一层嵌套 (常见的 providers.xxx / auth.xxx) */
|
|
162
|
+
function pickKey(obj, keys) {
|
|
163
|
+
if (!obj || typeof obj !== 'object')
|
|
164
|
+
return undefined;
|
|
165
|
+
for (const k of keys) {
|
|
166
|
+
const v = obj[k];
|
|
167
|
+
if (typeof v === 'string' && v.trim())
|
|
168
|
+
return v.trim();
|
|
169
|
+
}
|
|
170
|
+
for (const k of Object.keys(obj)) {
|
|
171
|
+
const sub = obj[k];
|
|
172
|
+
if (sub && typeof sub === 'object') {
|
|
173
|
+
for (const kk of keys) {
|
|
174
|
+
const v = sub[kk];
|
|
175
|
+
if (typeof v === 'string' && v.trim())
|
|
176
|
+
return v.trim();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
/** 把任意 provider 字符串解析成 Bolloon ModelProvider (未知 → 兜底 hint) */
|
|
183
|
+
export function resolveProvider(raw, hint) {
|
|
184
|
+
if (!raw)
|
|
185
|
+
return hint;
|
|
186
|
+
const norm = raw.trim().toLowerCase();
|
|
187
|
+
if (PROVIDER_ALIASES[norm]) {
|
|
188
|
+
return PROVIDER_ALIASES[norm];
|
|
189
|
+
}
|
|
190
|
+
// 容错: 包含关键字也识别
|
|
191
|
+
if (norm.includes('anthropic') || norm.includes('claude'))
|
|
192
|
+
return 'anthropic';
|
|
193
|
+
if (norm.includes('openai'))
|
|
194
|
+
return 'openai';
|
|
195
|
+
if (norm.includes('gemini') || norm.includes('google'))
|
|
196
|
+
return 'gemini';
|
|
197
|
+
if (norm.includes('ollama'))
|
|
198
|
+
return 'ollama';
|
|
199
|
+
if (norm.includes('deepseek'))
|
|
200
|
+
return 'deepseek';
|
|
201
|
+
if (norm.includes('minimax'))
|
|
202
|
+
return 'minimax';
|
|
203
|
+
if (norm.includes('openrouter'))
|
|
204
|
+
return 'openrouter';
|
|
205
|
+
return hint;
|
|
206
|
+
}
|
|
207
|
+
/** 把发现到的引擎映射成 provider 导入 patch (纯函数, 可单测) */
|
|
208
|
+
export function mapEngineToProviderConfig(engine) {
|
|
209
|
+
if (!engine.provider) {
|
|
210
|
+
throw new Error(`引擎 ${engine.id} 没有可映射的 provider, 无法导入为供应商`);
|
|
211
|
+
}
|
|
212
|
+
const patch = { enabled: true };
|
|
213
|
+
if (engine.apiKey)
|
|
214
|
+
patch.apiKey = engine.apiKey;
|
|
215
|
+
if (engine.baseUrl)
|
|
216
|
+
patch.baseUrl = engine.baseUrl;
|
|
217
|
+
if (engine.model)
|
|
218
|
+
patch.model = engine.model;
|
|
219
|
+
return { provider: engine.provider, patch };
|
|
220
|
+
}
|
|
221
|
+
/** 解析单个实验 API 文件内容 → 一组 {name, provider, apiKey, baseUrl, model, models?} */
|
|
222
|
+
export function parseExperimentFile(content) {
|
|
223
|
+
let json;
|
|
224
|
+
try {
|
|
225
|
+
json = JSON.parse(content);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
const out = [];
|
|
231
|
+
const pushOne = (obj, fallbackName) => {
|
|
232
|
+
if (!obj || typeof obj !== 'object')
|
|
233
|
+
return;
|
|
234
|
+
const provider = resolveProvider(pickKey(obj, PROVIDER_KEYS), 'openai');
|
|
235
|
+
const apiKey = pickKey(obj, APIKEY_KEYS);
|
|
236
|
+
const baseUrl = pickKey(obj, BASEURL_KEYS);
|
|
237
|
+
const model = pickKey(obj, MODEL_KEYS);
|
|
238
|
+
const models = Array.isArray(obj.models) ? obj.models.filter((m) => typeof m === 'string' && m.trim()) : undefined;
|
|
239
|
+
if (!apiKey && !baseUrl)
|
|
240
|
+
return; // 没任何连接信息, 跳过
|
|
241
|
+
out.push({
|
|
242
|
+
name: typeof obj.name === 'string' && obj.name.trim() ? obj.name.trim() : fallbackName,
|
|
243
|
+
provider,
|
|
244
|
+
apiKey,
|
|
245
|
+
baseUrl,
|
|
246
|
+
model,
|
|
247
|
+
models,
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
// 形态 1: 顶层直接是 { name, provider, apiKey, baseUrl, model }
|
|
251
|
+
if (json.name || json.provider || json.apiKey || json.baseUrl) {
|
|
252
|
+
pushOne(json, 'experiment');
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
// 形态 2: { providers: [...] } / { engines: [...] } / { apis: [...] }
|
|
256
|
+
for (const arrKey of ['providers', 'engines', 'apis', 'experiments']) {
|
|
257
|
+
if (Array.isArray(json[arrKey])) {
|
|
258
|
+
json[arrKey].forEach((e, i) => pushOne(e, `experiment-${arrKey}-${i}`));
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
// ====================== 核心发现逻辑 ======================
|
|
265
|
+
async function discoverOne(spec, deps) {
|
|
266
|
+
// 1. CLI 是否安装
|
|
267
|
+
let cliPath;
|
|
268
|
+
for (const bin of spec.binaries) {
|
|
269
|
+
const found = await deps.which(bin);
|
|
270
|
+
if (found) {
|
|
271
|
+
cliPath = found;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// 2. 读配置文件
|
|
276
|
+
let configObj = null;
|
|
277
|
+
let configPath;
|
|
278
|
+
for (const cf of spec.configFiles) {
|
|
279
|
+
const abs = path.isAbsolute(cf) ? cf : path.join(deps.home, cf);
|
|
280
|
+
const content = await deps.readFile(abs);
|
|
281
|
+
if (content) {
|
|
282
|
+
try {
|
|
283
|
+
configObj = JSON.parse(content);
|
|
284
|
+
configPath = abs;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// 非 JSON, 跳过
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// 3. 解析 apiKey: 环境变量优先, 其次配置文件
|
|
293
|
+
let apiKey;
|
|
294
|
+
let source = 'none';
|
|
295
|
+
for (const ek of spec.envKeys) {
|
|
296
|
+
const v = deps.env[ek];
|
|
297
|
+
if (v && v.trim()) {
|
|
298
|
+
apiKey = v.trim();
|
|
299
|
+
source = 'env';
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (!apiKey) {
|
|
304
|
+
const fromConfig = pickKey(configObj, APIKEY_KEYS);
|
|
305
|
+
if (fromConfig) {
|
|
306
|
+
apiKey = fromConfig;
|
|
307
|
+
source = 'config';
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// 4. baseUrl / model: 配置文件优先, 其次 hint
|
|
311
|
+
const baseUrl = pickKey(configObj, BASEURL_KEYS) || spec.baseUrlHint;
|
|
312
|
+
const model = pickKey(configObj, MODEL_KEYS) || spec.modelHint;
|
|
313
|
+
// 4.1 模型候选列表: 配置文件声明的 models 数组优先, 否则用规格预置列表
|
|
314
|
+
let models = spec.models;
|
|
315
|
+
if (configObj && Array.isArray(configObj.models) && configObj.models.length > 0) {
|
|
316
|
+
models = configObj.models.filter((m) => typeof m === 'string' && m.trim());
|
|
317
|
+
}
|
|
318
|
+
// 5. provider: 配置文件声明的 > hint
|
|
319
|
+
const provider = resolveProvider(pickKey(configObj, PROVIDER_KEYS), spec.providerHint);
|
|
320
|
+
const configured = !!apiKey || provider === 'ollama' || provider === 'local';
|
|
321
|
+
const available = !!cliPath && configured;
|
|
322
|
+
return {
|
|
323
|
+
id: spec.id,
|
|
324
|
+
displayName: spec.displayName,
|
|
325
|
+
installed: !!cliPath,
|
|
326
|
+
configured,
|
|
327
|
+
available,
|
|
328
|
+
cliPath,
|
|
329
|
+
configPath,
|
|
330
|
+
provider,
|
|
331
|
+
apiKey,
|
|
332
|
+
baseUrl,
|
|
333
|
+
model,
|
|
334
|
+
models,
|
|
335
|
+
source,
|
|
336
|
+
notes: `委派参数模板: ${spec.binaries[0]} ${spec.delegateArgs('<prompt>').join(' ')}`,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
async function discoverExperimentEngines(deps) {
|
|
340
|
+
const dir = deps.env['BOLLOON_EXPERIMENT_API_DIR'] ||
|
|
341
|
+
path.join(deps.home, '.bolloon', 'experiments');
|
|
342
|
+
const files = await deps.readdir(dir);
|
|
343
|
+
if (!files || files.length === 0)
|
|
344
|
+
return [];
|
|
345
|
+
const result = [];
|
|
346
|
+
for (const f of files) {
|
|
347
|
+
if (!f.endsWith('.json'))
|
|
348
|
+
continue;
|
|
349
|
+
const content = await deps.readFile(path.join(dir, f));
|
|
350
|
+
if (!content)
|
|
351
|
+
continue;
|
|
352
|
+
const parsed = parseExperimentFile(content);
|
|
353
|
+
for (const item of parsed) {
|
|
354
|
+
const provider = item.provider || 'openai';
|
|
355
|
+
result.push({
|
|
356
|
+
id: `experiment:${item.name}`,
|
|
357
|
+
displayName: `实验 API: ${item.name}`,
|
|
358
|
+
installed: true, // 实验 API 视为"已装" (它们就是配置文件声明的)
|
|
359
|
+
configured: !!(item.apiKey || item.baseUrl),
|
|
360
|
+
available: !!(item.apiKey || item.baseUrl),
|
|
361
|
+
configPath: path.join(dir, f),
|
|
362
|
+
provider,
|
|
363
|
+
apiKey: item.apiKey,
|
|
364
|
+
baseUrl: item.baseUrl,
|
|
365
|
+
model: item.model,
|
|
366
|
+
models: item.models,
|
|
367
|
+
source: 'config',
|
|
368
|
+
notes: '来自实验目录声明的 API (BOLLOON_EXPERIMENT_API_DIR)',
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return result;
|
|
373
|
+
}
|
|
374
|
+
/** 发现所有外部引擎 (已知 + 实验) */
|
|
375
|
+
export async function discoverEngines(deps = defaultDeps()) {
|
|
376
|
+
const known = await Promise.all(KNOWN_ENGINES.map((spec) => discoverOne(spec, deps)));
|
|
377
|
+
const experiment = await discoverExperimentEngines(deps);
|
|
378
|
+
return [...known, ...experiment];
|
|
379
|
+
}
|
|
380
|
+
/** 取单个引擎规格 (委派时用) */
|
|
381
|
+
export function getEngineSpec(id) {
|
|
382
|
+
return KNOWN_ENGINES.find((e) => e.id === id);
|
|
383
|
+
}
|
|
384
|
+
/** 取已知引擎的委派 argv (best-effort); 传 model 时追加该引擎的 modelFlag */
|
|
385
|
+
export function buildDelegateArgs(id, prompt, model) {
|
|
386
|
+
const spec = getEngineSpec(id);
|
|
387
|
+
if (!spec)
|
|
388
|
+
return undefined;
|
|
389
|
+
const args = spec.delegateArgs(prompt);
|
|
390
|
+
if (model && spec.modelFlag) {
|
|
391
|
+
args.push(spec.modelFlag, model);
|
|
392
|
+
}
|
|
393
|
+
return args;
|
|
394
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* external-engines/index.ts — 外部编码智能体 模块 barrel
|
|
3
|
+
*
|
|
4
|
+
* 对外暴露: 发现 (discover) / 配置为供应商映射 (mapEngineToProviderConfig) /
|
|
5
|
+
* 委派 (delegateToEngine) / 类型.
|
|
6
|
+
*/
|
|
7
|
+
export * from './types.js';
|
|
8
|
+
export { discoverEngines, getEngineSpec, buildDelegateArgs, mapEngineToProviderConfig, parseExperimentFile, resolveProvider, defaultDeps, } from './discovery.js';
|
|
9
|
+
export { delegateToEngine } from './delegate.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* external-engines/types.ts — 外部编码智能体/工具 类型定义
|
|
3
|
+
*
|
|
4
|
+
* "外部引擎" = 本机已安装的其他 AI 编码工具 (codex / claude code / openclaw /
|
|
5
|
+
* hermes / opencode) 以及实验里已声明的 API. Bolloon 可以:
|
|
6
|
+
* 1. 发现 (discover): 扫描 CLI 是否安装 + 它们的配置文件/环境变量里已有的 API key
|
|
7
|
+
* 2. 配置为供应商 (import): 把发现到的 API key/baseUrl/model 写进 Bolloon 的
|
|
8
|
+
* LLM provider 体系, 当作普通供应商启用 (用户无需重复填 key)
|
|
9
|
+
* 3. 委派 (delegate): 直接调用这些工具的 CLI, 把编码任务派发给它们当子智能体跑
|
|
10
|
+
*/
|
|
11
|
+
export {};
|