tiger-agent 0.2.5 → 0.3.2
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/.env.example +19 -0
- package/.env.secrets.example +3 -0
- package/README.md +373 -6
- package/package.json +1 -1
- package/scripts/onboard.js +156 -59
- package/src/agent/skills.js +1 -1
- package/src/apiProviders.js +10 -11
- package/src/apiProviders.js.bak +222 -0
- package/src/cli.js +21 -0
- package/src/config.js +19 -0
- package/src/llmClient.js +11 -1
- package/src/swarm/agentRuntime.js +836 -0
- package/src/swarm/agentRuntime.js.bak +456 -0
- package/src/swarm/configStore.js +360 -0
- package/src/swarm/index.js +25 -0
- package/src/swarm/taskBus.js +246 -0
- package/src/telegram/bot.js +330 -2
- package/src/telegram/supervisor.js +53 -2
package/scripts/onboard.js
CHANGED
|
@@ -60,6 +60,19 @@ function envLine(k, v) {
|
|
|
60
60
|
return `${k}=${s}`;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
const KNOWN_PROVIDERS = ['minimax', 'zai', 'claude', 'kimi', 'moonshot'];
|
|
64
|
+
|
|
65
|
+
function parseProviderList(input, fallback = []) {
|
|
66
|
+
const raw = String(input || '').trim();
|
|
67
|
+
const values = (raw ? raw : fallback.join(','))
|
|
68
|
+
.split(',')
|
|
69
|
+
.map((s) => s.trim().toLowerCase())
|
|
70
|
+
.filter(Boolean);
|
|
71
|
+
const unique = [...new Set(values)];
|
|
72
|
+
const invalid = unique.filter((p) => !KNOWN_PROVIDERS.includes(p));
|
|
73
|
+
return { providers: unique, invalid };
|
|
74
|
+
}
|
|
75
|
+
|
|
63
76
|
// ─── Daemon helpers ───────────────────────────────────────────────────────────
|
|
64
77
|
|
|
65
78
|
function nodeBin() {
|
|
@@ -180,32 +193,87 @@ Config will be saved to: ${TIGER_HOME}
|
|
|
180
193
|
if (!yn(ow, false)) { console.log('Cancelled.'); rl.close(); return; }
|
|
181
194
|
}
|
|
182
195
|
|
|
183
|
-
// ──
|
|
184
|
-
console.log('\nAvailable providers:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
196
|
+
// ── Provider selection / routing ──────────────────────────────────────────
|
|
197
|
+
console.log('\nAvailable providers: minimax, zai (Zhipu GLM-4.7), claude, kimi, moonshot');
|
|
198
|
+
console.log('Choose only providers you want to configure. Others will be omitted from .env.');
|
|
199
|
+
|
|
200
|
+
let selectedProviders = [];
|
|
201
|
+
while (!selectedProviders.length) {
|
|
202
|
+
const picked = await ask('Providers to configure (comma-separated, default: minimax): ');
|
|
203
|
+
const parsed = parseProviderList(picked, ['minimax']);
|
|
204
|
+
if (parsed.invalid.length) {
|
|
205
|
+
console.log(`Invalid provider(s): ${parsed.invalid.join(', ')}. Try again.`);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (!parsed.providers.length) {
|
|
209
|
+
console.log('Pick at least one provider.');
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
selectedProviders = parsed.providers;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const activeDefault = selectedProviders[0];
|
|
216
|
+
let activeProv = '';
|
|
217
|
+
while (!activeProv) {
|
|
218
|
+
const candidate = (await ask(`Active provider (${activeDefault}): `)).trim().toLowerCase() || activeDefault;
|
|
219
|
+
if (!selectedProviders.includes(candidate)) {
|
|
220
|
+
console.log(`Active provider must be one of: ${selectedProviders.join(', ')}`);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
activeProv = candidate;
|
|
224
|
+
}
|
|
188
225
|
|
|
189
|
-
|
|
190
|
-
|
|
226
|
+
const orderDefault = [activeProv, ...selectedProviders.filter((p) => p !== activeProv)].join(',');
|
|
227
|
+
let provOrder = '';
|
|
228
|
+
while (!provOrder) {
|
|
229
|
+
const input = await ask(`Provider fallback order (${orderDefault}): `);
|
|
230
|
+
const parsed = parseProviderList(input, [activeProv, ...selectedProviders.filter((p) => p !== activeProv)]);
|
|
231
|
+
if (parsed.invalid.length) {
|
|
232
|
+
console.log(`Invalid provider(s): ${parsed.invalid.join(', ')}. Try again.`);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const outsideSelection = parsed.providers.filter((p) => !selectedProviders.includes(p));
|
|
236
|
+
if (outsideSelection.length) {
|
|
237
|
+
console.log(`Order can only include selected providers: ${selectedProviders.join(', ')}`);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (!parsed.providers.includes(activeProv)) {
|
|
241
|
+
console.log(`Order must include active provider: ${activeProv}`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
provOrder = parsed.providers.join(',');
|
|
245
|
+
}
|
|
191
246
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const
|
|
247
|
+
// ── API keys ───────────────────────────────────────────────────────────────
|
|
248
|
+
console.log('\nEnter API keys for selected providers:');
|
|
249
|
+
const kimiKey = selectedProviders.includes('kimi') ? (await askHidden(' KIMI_CODE_API_KEY : ')).trim() : '';
|
|
250
|
+
const moonshotKey = selectedProviders.includes('moonshot') ? (await askHidden(' MOONSHOT_API_KEY : ')).trim() : '';
|
|
251
|
+
const zaiKey = selectedProviders.includes('zai') ? (await askHidden(' ZAI_API_KEY : ')).trim() : '';
|
|
252
|
+
const minimaxKey = selectedProviders.includes('minimax') ? (await askHidden(' MINIMAX_API_KEY : ')).trim() : '';
|
|
253
|
+
const claudeKey = selectedProviders.includes('claude') ? (await askHidden(' CLAUDE_API_KEY : ')).trim() : '';
|
|
197
254
|
|
|
198
255
|
// ── Telegram ───────────────────────────────────────────────────────────────
|
|
199
256
|
console.log('');
|
|
200
257
|
const tgToken = (await askHidden(' TELEGRAM_BOT_TOKEN : ')).trim();
|
|
201
258
|
|
|
202
259
|
// ── Token limits ───────────────────────────────────────────────────────────
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
260
|
+
const tokenLimits = {};
|
|
261
|
+
console.log('\nDaily token limits for selected providers (0 = unlimited, auto-switch on breach):');
|
|
262
|
+
if (selectedProviders.includes('kimi')) {
|
|
263
|
+
tokenLimits.kimi = (await ask(' KIMI_TOKEN_LIMIT (100000): ')).trim() || '100000';
|
|
264
|
+
}
|
|
265
|
+
if (selectedProviders.includes('moonshot')) {
|
|
266
|
+
tokenLimits.moonshot = (await ask(' MOONSHOT_TOKEN_LIMIT(100000): ')).trim() || '100000';
|
|
267
|
+
}
|
|
268
|
+
if (selectedProviders.includes('zai')) {
|
|
269
|
+
tokenLimits.zai = (await ask(' ZAI_TOKEN_LIMIT (100000): ')).trim() || '100000';
|
|
270
|
+
}
|
|
271
|
+
if (selectedProviders.includes('minimax')) {
|
|
272
|
+
tokenLimits.minimax = (await ask(' MINIMAX_TOKEN_LIMIT (100000): ')).trim() || '100000';
|
|
273
|
+
}
|
|
274
|
+
if (selectedProviders.includes('claude')) {
|
|
275
|
+
tokenLimits.claude = (await ask(' CLAUDE_TOKEN_LIMIT (500000): ')).trim() || '500000';
|
|
276
|
+
}
|
|
209
277
|
|
|
210
278
|
// ── Misc ───────────────────────────────────────────────────────────────────
|
|
211
279
|
const allowShell = yn(await ask('\nEnable shell tool? (y/N): '), false);
|
|
@@ -215,51 +283,80 @@ Config will be saved to: ${TIGER_HOME}
|
|
|
215
283
|
const lines = [
|
|
216
284
|
'# Tiger Agent config — generated by `tiger onboard`',
|
|
217
285
|
'',
|
|
218
|
-
'# ── Legacy Kimi compat (used if ACTIVE_PROVIDER=kimi)',
|
|
219
|
-
envLine('KIMI_PROVIDER', 'code'),
|
|
220
|
-
envLine('KIMI_CODE_API_KEY', kimiKey),
|
|
221
|
-
envLine('KIMI_BASE_URL', 'https://api.kimi.com/coding/v1'),
|
|
222
|
-
envLine('KIMI_CHAT_MODEL', 'kimi-coding/k2p5'),
|
|
223
|
-
envLine('KIMI_EMBED_MODEL', ''),
|
|
224
|
-
envLine('KIMI_USER_AGENT', 'KimiCLI/0.77'),
|
|
225
|
-
envLine('KIMI_ENABLE_EMBEDDINGS', 'false'),
|
|
226
|
-
envLine('KIMI_TIMEOUT_MS', '30000'),
|
|
227
|
-
'',
|
|
228
286
|
'# ── Multi-provider',
|
|
229
287
|
envLine('ACTIVE_PROVIDER', activeProv),
|
|
230
288
|
envLine('PROVIDER_ORDER', provOrder),
|
|
231
|
-
''
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
289
|
+
''
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
if (selectedProviders.includes('kimi')) {
|
|
293
|
+
lines.push(
|
|
294
|
+
'# ── Legacy Kimi compat (used if ACTIVE_PROVIDER=kimi)',
|
|
295
|
+
envLine('KIMI_PROVIDER', 'code'),
|
|
296
|
+
envLine('KIMI_CODE_API_KEY', kimiKey),
|
|
297
|
+
envLine('KIMI_BASE_URL', 'https://api.kimi.com/coding/v1'),
|
|
298
|
+
envLine('KIMI_CHAT_MODEL', 'kimi-coding/k2p5'),
|
|
299
|
+
envLine('KIMI_EMBED_MODEL', ''),
|
|
300
|
+
envLine('KIMI_USER_AGENT', 'KimiCLI/0.77'),
|
|
301
|
+
envLine('KIMI_ENABLE_EMBEDDINGS', 'false'),
|
|
302
|
+
envLine('KIMI_TIMEOUT_MS', '30000'),
|
|
303
|
+
''
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (selectedProviders.includes('zai')) {
|
|
308
|
+
lines.push(
|
|
309
|
+
'# ── Z.ai (Zhipu GLM)',
|
|
310
|
+
envLine('ZAI_API_KEY', zaiKey),
|
|
311
|
+
envLine('ZAI_BASE_URL', 'https://api.z.ai/api/coding/paas/v4'),
|
|
312
|
+
envLine('ZAI_MODEL', 'glm-4.7'),
|
|
313
|
+
envLine('ZAI_TIMEOUT_MS', '30000'),
|
|
314
|
+
''
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (selectedProviders.includes('minimax')) {
|
|
319
|
+
lines.push(
|
|
320
|
+
'# ── MiniMax (Coding / OpenAI-compatible)',
|
|
321
|
+
envLine('MINIMAX_API_KEY', minimaxKey),
|
|
322
|
+
envLine('MINIMAX_BASE_URL', 'https://api.minimax.io/v1'),
|
|
323
|
+
envLine('MINIMAX_MODEL', 'MiniMax-M2.5'),
|
|
324
|
+
envLine('MINIMAX_TIMEOUT_MS', '30000'),
|
|
325
|
+
''
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (selectedProviders.includes('claude')) {
|
|
330
|
+
lines.push(
|
|
331
|
+
'# ── Claude (Anthropic)',
|
|
332
|
+
envLine('CLAUDE_API_KEY', claudeKey),
|
|
333
|
+
envLine('CLAUDE_MODEL', 'claude-sonnet-4-6'),
|
|
334
|
+
envLine('CLAUDE_TIMEOUT_MS', '60000'),
|
|
335
|
+
''
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (selectedProviders.includes('moonshot')) {
|
|
340
|
+
lines.push(
|
|
341
|
+
'# ── Moonshot',
|
|
342
|
+
envLine('MOONSHOT_API_KEY', moonshotKey),
|
|
343
|
+
envLine('MOONSHOT_BASE_URL', 'https://api.moonshot.cn/v1'),
|
|
344
|
+
envLine('MOONSHOT_MODEL', 'kimi-k1'),
|
|
345
|
+
''
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
lines.push('# ── Token limits (daily, 0 = unlimited)');
|
|
350
|
+
if (tokenLimits.kimi != null) lines.push(envLine('KIMI_TOKEN_LIMIT', tokenLimits.kimi));
|
|
351
|
+
if (tokenLimits.moonshot != null) lines.push(envLine('MOONSHOT_TOKEN_LIMIT', tokenLimits.moonshot));
|
|
352
|
+
if (tokenLimits.zai != null) lines.push(envLine('ZAI_TOKEN_LIMIT', tokenLimits.zai));
|
|
353
|
+
if (tokenLimits.minimax != null) lines.push(envLine('MINIMAX_TOKEN_LIMIT', tokenLimits.minimax));
|
|
354
|
+
if (tokenLimits.claude != null) lines.push(envLine('CLAUDE_TOKEN_LIMIT', tokenLimits.claude));
|
|
355
|
+
lines.push(
|
|
260
356
|
'',
|
|
261
357
|
'# ── Telegram',
|
|
262
358
|
envLine('TELEGRAM_BOT_TOKEN', tgToken),
|
|
359
|
+
envLine('SWARM_ENABLED', 'false'),
|
|
263
360
|
'',
|
|
264
361
|
'# ── Permissions',
|
|
265
362
|
envLine('ALLOW_SHELL', allowShell ? 'true' : 'false'),
|
|
@@ -280,7 +377,7 @@ Config will be saved to: ${TIGER_HOME}
|
|
|
280
377
|
'MEMORY_INGEST_EVERY_TURNS=2',
|
|
281
378
|
'MEMORY_INGEST_MIN_CHARS=140',
|
|
282
379
|
''
|
|
283
|
-
|
|
380
|
+
);
|
|
284
381
|
|
|
285
382
|
fs.writeFileSync(ENV_PATH, lines.join('\n'), { mode: 0o600 });
|
|
286
383
|
console.log(`\n✅ Config written to ${ENV_PATH}`);
|
|
@@ -307,7 +404,7 @@ Setup complete! 🐯
|
|
|
307
404
|
Start CLI: tiger start
|
|
308
405
|
Start Telegram: tiger telegram
|
|
309
406
|
Background daemon: tiger telegram --background
|
|
310
|
-
Switch provider: /api
|
|
407
|
+
Switch provider: /api <provider_id> (in Telegram chat)
|
|
311
408
|
Token usage: /tokens (in Telegram chat)
|
|
312
409
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
313
410
|
|
package/src/agent/skills.js
CHANGED
|
@@ -135,7 +135,7 @@ async function clawhubInstall(args = {}) {
|
|
|
135
135
|
if (force) argv.push('--force');
|
|
136
136
|
|
|
137
137
|
const res = await runClawhub(argv, {
|
|
138
|
-
timeout: Number(args.timeout_ms ||
|
|
138
|
+
timeout: Number(args.timeout_ms || process.env.SWARM_AGENT_TIMEOUT_MS || 720000),
|
|
139
139
|
maxBuffer: 1024 * 1024
|
|
140
140
|
});
|
|
141
141
|
if (res.ok) {
|
package/src/apiProviders.js
CHANGED
|
@@ -134,7 +134,7 @@ function buildProviders(env) {
|
|
|
134
134
|
embedPath: '/embeddings',
|
|
135
135
|
formatRequest: standardFormat,
|
|
136
136
|
parseResponse: standardParse,
|
|
137
|
-
timeout: Number(env.KIMI_TIMEOUT_MS ||
|
|
137
|
+
timeout: Number(env.KIMI_TIMEOUT_MS || 180000)
|
|
138
138
|
},
|
|
139
139
|
|
|
140
140
|
moonshot: {
|
|
@@ -150,7 +150,7 @@ function buildProviders(env) {
|
|
|
150
150
|
embedPath: '/embeddings',
|
|
151
151
|
formatRequest: standardFormat,
|
|
152
152
|
parseResponse: standardParse,
|
|
153
|
-
timeout: Number(env.KIMI_TIMEOUT_MS ||
|
|
153
|
+
timeout: Number(env.KIMI_TIMEOUT_MS || 180000)
|
|
154
154
|
},
|
|
155
155
|
|
|
156
156
|
zai: {
|
|
@@ -171,14 +171,14 @@ function buildProviders(env) {
|
|
|
171
171
|
embedPath: '/embeddings',
|
|
172
172
|
formatRequest: standardFormat,
|
|
173
173
|
parseResponse: standardParse,
|
|
174
|
-
timeout: Number(env.ZAI_TIMEOUT_MS ||
|
|
174
|
+
timeout: Number(env.ZAI_TIMEOUT_MS || 180000)
|
|
175
175
|
},
|
|
176
176
|
|
|
177
177
|
minimax: {
|
|
178
178
|
id: 'minimax',
|
|
179
179
|
name: 'MiniMax',
|
|
180
|
-
baseUrl: (env.MINIMAX_BASE_URL || 'https://api.minimax.
|
|
181
|
-
chatModel: env.MINIMAX_MODEL || '
|
|
180
|
+
baseUrl: (env.MINIMAX_BASE_URL || 'https://api.minimax.io/v1').replace(/\/$/, ''),
|
|
181
|
+
chatModel: env.MINIMAX_MODEL || 'MiniMax-M2.5',
|
|
182
182
|
embedModel: env.MINIMAX_EMBED_MODEL || '',
|
|
183
183
|
apiKey: env.MINIMAX_API_KEY || '',
|
|
184
184
|
userAgent: '',
|
|
@@ -187,7 +187,7 @@ function buildProviders(env) {
|
|
|
187
187
|
embedPath: '/embeddings',
|
|
188
188
|
formatRequest: standardFormat,
|
|
189
189
|
parseResponse: standardParse,
|
|
190
|
-
timeout: Number(env.MINIMAX_TIMEOUT_MS ||
|
|
190
|
+
timeout: Number(env.MINIMAX_TIMEOUT_MS || 180000)
|
|
191
191
|
},
|
|
192
192
|
|
|
193
193
|
claude: {
|
|
@@ -203,16 +203,15 @@ function buildProviders(env) {
|
|
|
203
203
|
embedPath: null, // Claude does not expose an embeddings endpoint
|
|
204
204
|
formatRequest: claudeFormat,
|
|
205
205
|
parseResponse: claudeParse,
|
|
206
|
-
timeout: Number(env.CLAUDE_TIMEOUT_MS ||
|
|
206
|
+
timeout: Number(env.CLAUDE_TIMEOUT_MS || 180000)
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
//
|
|
212
|
-
|
|
211
|
+
// Always rebuild from current process.env so runtime .env changes take effect
|
|
212
|
+
// (fixes stale timeout after PM2 restart or .env update)
|
|
213
213
|
function getProviders() {
|
|
214
|
-
|
|
215
|
-
return _providers;
|
|
214
|
+
return buildProviders(process.env);
|
|
216
215
|
}
|
|
217
216
|
|
|
218
217
|
function getProvider(id) {
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
// ─── Zhipu AI (BigModel) JWT auth ──────────────────────────────────────────
|
|
6
|
+
// Their v4 API requires HS256 JWT derived from the api-key (format: "id.secret")
|
|
7
|
+
function b64url(buf) {
|
|
8
|
+
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function zhipuJwt(apiKey) {
|
|
12
|
+
const dot = apiKey.indexOf('.');
|
|
13
|
+
if (dot === -1) return apiKey; // fallback: treat as plain token
|
|
14
|
+
const id = apiKey.slice(0, dot);
|
|
15
|
+
const secret = apiKey.slice(dot + 1);
|
|
16
|
+
const now = Math.floor(Date.now() / 1000);
|
|
17
|
+
const hdr = b64url(Buffer.from(JSON.stringify({ alg: 'HS256', sign_type: 'SIGN' })));
|
|
18
|
+
const pay = b64url(Buffer.from(JSON.stringify({ api_key: id, exp: now + 3600, timestamp: now })));
|
|
19
|
+
const sig = b64url(crypto.createHmac('sha256', secret).update(`${hdr}.${pay}`).digest());
|
|
20
|
+
return `${hdr}.${pay}.${sig}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ─── OpenAI-compatible adapters ────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function standardFormat(messages, options) {
|
|
26
|
+
const payload = {
|
|
27
|
+
model: options.model,
|
|
28
|
+
messages,
|
|
29
|
+
temperature: options.temperature ?? 0.3
|
|
30
|
+
};
|
|
31
|
+
if (options.tools && options.tools.length) payload.tools = options.tools;
|
|
32
|
+
if (options.tool_choice) payload.tool_choice = options.tool_choice;
|
|
33
|
+
return payload;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function standardParse(data) {
|
|
37
|
+
const message = data.choices?.[0]?.message || {};
|
|
38
|
+
const u = data.usage || {};
|
|
39
|
+
const tokens = (u.prompt_tokens || 0) + (u.completion_tokens || 0);
|
|
40
|
+
return { message, tokens };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Claude (Anthropic) adapters ───────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
function claudeFormat(messages, options) {
|
|
46
|
+
const systemMsg = messages.find((m) => m.role === 'system');
|
|
47
|
+
const rest = messages.filter((m) => m.role !== 'system');
|
|
48
|
+
|
|
49
|
+
// Convert tool definitions: OpenAI → Claude
|
|
50
|
+
let tools;
|
|
51
|
+
if (options.tools && options.tools.length) {
|
|
52
|
+
tools = options.tools.map((t) => ({
|
|
53
|
+
name: t.function.name,
|
|
54
|
+
description: t.function.description || '',
|
|
55
|
+
input_schema: t.function.parameters || { type: 'object', properties: {} }
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Convert message content: tool_calls & tool results
|
|
60
|
+
const converted = rest.map((m) => {
|
|
61
|
+
if (m.role === 'assistant' && Array.isArray(m.tool_calls) && m.tool_calls.length) {
|
|
62
|
+
const content = [];
|
|
63
|
+
if (m.content) content.push({ type: 'text', text: m.content });
|
|
64
|
+
for (const tc of m.tool_calls) {
|
|
65
|
+
let input = {};
|
|
66
|
+
try { input = JSON.parse(tc.function.arguments || '{}'); } catch (_) {}
|
|
67
|
+
content.push({ type: 'tool_use', id: tc.id, name: tc.function.name, input });
|
|
68
|
+
}
|
|
69
|
+
return { role: 'assistant', content };
|
|
70
|
+
}
|
|
71
|
+
if (m.role === 'tool') {
|
|
72
|
+
return {
|
|
73
|
+
role: 'user',
|
|
74
|
+
content: [{ type: 'tool_result', tool_use_id: m.tool_call_id, content: String(m.content || '') }]
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return m;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const payload = {
|
|
81
|
+
model: options.model,
|
|
82
|
+
max_tokens: options.max_tokens || 8192,
|
|
83
|
+
messages: converted
|
|
84
|
+
};
|
|
85
|
+
if (systemMsg) payload.system = systemMsg.content;
|
|
86
|
+
if (tools && tools.length) {
|
|
87
|
+
payload.tools = tools;
|
|
88
|
+
// Convert OpenAI tool_choice format → Claude format
|
|
89
|
+
const tc = options.tool_choice;
|
|
90
|
+
if (tc && tc !== 'none') {
|
|
91
|
+
if (tc === 'auto' || tc === 'required') {
|
|
92
|
+
payload.tool_choice = { type: tc === 'required' ? 'any' : 'auto' };
|
|
93
|
+
} else if (tc && typeof tc === 'object' && tc.type === 'function') {
|
|
94
|
+
payload.tool_choice = { type: 'tool', name: tc.function.name };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return payload;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function claudeParse(data) {
|
|
102
|
+
const content = Array.isArray(data.content) ? data.content : [];
|
|
103
|
+
const textBlock = content.find((b) => b.type === 'text');
|
|
104
|
+
const toolUseBlocks = content.filter((b) => b.type === 'tool_use');
|
|
105
|
+
|
|
106
|
+
const message = { role: 'assistant', content: textBlock ? textBlock.text : '' };
|
|
107
|
+
if (toolUseBlocks.length) {
|
|
108
|
+
message.tool_calls = toolUseBlocks.map((tb) => ({
|
|
109
|
+
id: tb.id,
|
|
110
|
+
type: 'function',
|
|
111
|
+
function: { name: tb.name, arguments: JSON.stringify(tb.input || {}) }
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const u = data.usage || {};
|
|
116
|
+
const tokens = (u.input_tokens || 0) + (u.output_tokens || 0);
|
|
117
|
+
return { message, tokens };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Provider registry ─────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
function buildProviders(env) {
|
|
123
|
+
return {
|
|
124
|
+
kimi: {
|
|
125
|
+
id: 'kimi',
|
|
126
|
+
name: 'Kimi Code',
|
|
127
|
+
baseUrl: (env.KIMI_BASE_URL || 'https://api.kimi.com/coding/v1').replace(/\/$/, ''),
|
|
128
|
+
chatModel: env.KIMI_CHAT_MODEL ? env.KIMI_CHAT_MODEL.replace(/^kimi-coding\//, '') : 'k2p5',
|
|
129
|
+
embedModel: env.KIMI_EMBED_MODEL || '',
|
|
130
|
+
apiKey: env.KIMI_CODE_API_KEY || env.KIMI_API_KEY || '',
|
|
131
|
+
userAgent: env.KIMI_USER_AGENT || 'KimiCLI/0.77',
|
|
132
|
+
authHeaders: (key) => ({ Authorization: `Bearer ${key}` }),
|
|
133
|
+
chatPath: '/chat/completions',
|
|
134
|
+
embedPath: '/embeddings',
|
|
135
|
+
formatRequest: standardFormat,
|
|
136
|
+
parseResponse: standardParse,
|
|
137
|
+
timeout: Number(env.KIMI_TIMEOUT_MS || 30000)
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
moonshot: {
|
|
141
|
+
id: 'moonshot',
|
|
142
|
+
name: 'Kimi Moonshot',
|
|
143
|
+
baseUrl: (env.MOONSHOT_BASE_URL || 'https://api.moonshot.cn/v1').replace(/\/$/, ''),
|
|
144
|
+
chatModel: env.MOONSHOT_MODEL || 'kimi-k1',
|
|
145
|
+
embedModel: env.MOONSHOT_EMBED_MODEL || 'kimi-embedding-v1',
|
|
146
|
+
apiKey: env.MOONSHOT_API_KEY || env.KIMI_API_KEY || '',
|
|
147
|
+
userAgent: '',
|
|
148
|
+
authHeaders: (key) => ({ Authorization: `Bearer ${key}` }),
|
|
149
|
+
chatPath: '/chat/completions',
|
|
150
|
+
embedPath: '/embeddings',
|
|
151
|
+
formatRequest: standardFormat,
|
|
152
|
+
parseResponse: standardParse,
|
|
153
|
+
timeout: Number(env.KIMI_TIMEOUT_MS || 30000)
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
zai: {
|
|
157
|
+
id: 'zai',
|
|
158
|
+
name: 'Z.ai',
|
|
159
|
+
baseUrl: (env.ZAI_BASE_URL || 'https://api.z.ai/api/coding/paas/v4').replace(/\/$/, ''),
|
|
160
|
+
chatModel: env.ZAI_MODEL || 'glm-4.7',
|
|
161
|
+
embedModel: env.ZAI_EMBED_MODEL || '',
|
|
162
|
+
apiKey: env.ZAI_API_KEY || '',
|
|
163
|
+
userAgent: '',
|
|
164
|
+
// api.z.ai uses plain Bearer; old bigmodel.cn used Zhipu JWT
|
|
165
|
+
authHeaders: (key) => {
|
|
166
|
+
const baseUrl = (env.ZAI_BASE_URL || '').toLowerCase();
|
|
167
|
+
if (baseUrl.includes('bigmodel.cn')) return { Authorization: `Bearer ${zhipuJwt(key)}` };
|
|
168
|
+
return { Authorization: `Bearer ${key}` };
|
|
169
|
+
},
|
|
170
|
+
chatPath: '/chat/completions',
|
|
171
|
+
embedPath: '/embeddings',
|
|
172
|
+
formatRequest: standardFormat,
|
|
173
|
+
parseResponse: standardParse,
|
|
174
|
+
timeout: Number(env.ZAI_TIMEOUT_MS || 30000)
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
minimax: {
|
|
178
|
+
id: 'minimax',
|
|
179
|
+
name: 'MiniMax',
|
|
180
|
+
baseUrl: (env.MINIMAX_BASE_URL || 'https://api.minimax.chat/v1').replace(/\/$/, ''),
|
|
181
|
+
chatModel: env.MINIMAX_MODEL || 'abab6.5s-chat',
|
|
182
|
+
embedModel: env.MINIMAX_EMBED_MODEL || '',
|
|
183
|
+
apiKey: env.MINIMAX_API_KEY || '',
|
|
184
|
+
userAgent: '',
|
|
185
|
+
authHeaders: (key) => ({ Authorization: `Bearer ${key}` }),
|
|
186
|
+
chatPath: '/chat/completions',
|
|
187
|
+
embedPath: '/embeddings',
|
|
188
|
+
formatRequest: standardFormat,
|
|
189
|
+
parseResponse: standardParse,
|
|
190
|
+
timeout: Number(env.MINIMAX_TIMEOUT_MS || 30000)
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
claude: {
|
|
194
|
+
id: 'claude',
|
|
195
|
+
name: 'Claude (Anthropic)',
|
|
196
|
+
baseUrl: (env.CLAUDE_BASE_URL || 'https://api.anthropic.com').replace(/\/$/, ''),
|
|
197
|
+
chatModel: env.CLAUDE_MODEL || 'claude-sonnet-4-6',
|
|
198
|
+
embedModel: '',
|
|
199
|
+
apiKey: env.CLAUDE_API_KEY || env.ANTHROPIC_API_KEY || '',
|
|
200
|
+
userAgent: '',
|
|
201
|
+
authHeaders: (key) => ({ 'x-api-key': key, 'anthropic-version': '2023-06-01' }),
|
|
202
|
+
chatPath: '/v1/messages',
|
|
203
|
+
embedPath: null, // Claude does not expose an embeddings endpoint
|
|
204
|
+
formatRequest: claudeFormat,
|
|
205
|
+
parseResponse: claudeParse,
|
|
206
|
+
timeout: Number(env.CLAUDE_TIMEOUT_MS || 60000)
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Singleton — providers are built once from process.env on first access
|
|
212
|
+
let _providers = null;
|
|
213
|
+
function getProviders() {
|
|
214
|
+
if (!_providers) _providers = buildProviders(process.env);
|
|
215
|
+
return _providers;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function getProvider(id) {
|
|
219
|
+
return getProviders()[id] || null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
module.exports = { getProviders, getProvider };
|
package/src/cli.js
CHANGED
|
@@ -9,12 +9,14 @@ const { startReflectionScheduler } = require('./agent/reflectionScheduler');
|
|
|
9
9
|
const { initVectorMemory } = require('./agent/db');
|
|
10
10
|
const { startTelegramBot } = require('./telegram/bot');
|
|
11
11
|
const { handleMessage } = require('./agent/mainAgent');
|
|
12
|
+
const { ensureSwarmLayout } = require('./swarm');
|
|
12
13
|
|
|
13
14
|
// Source root — always inside the npm package
|
|
14
15
|
const srcRoot = path.resolve(__dirname, '..');
|
|
15
16
|
// Runtime root — inside TIGER_HOME when installed globally, otherwise project root
|
|
16
17
|
const rootDir = process.env.TIGER_HOME || process.cwd();
|
|
17
18
|
const supervisorPidPath = path.resolve(rootDir, 'tiger-telegram.pid');
|
|
19
|
+
const workerHeartbeatPath = path.resolve(rootDir, 'tiger-telegram-worker.heartbeat');
|
|
18
20
|
|
|
19
21
|
process.on('unhandledRejection', (reason) => {
|
|
20
22
|
const msg = reason && reason.stack ? reason.stack : String(reason);
|
|
@@ -33,6 +35,14 @@ function hasFlag(argv, flag) {
|
|
|
33
35
|
return argv.includes(flag);
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
function writeWorkerHeartbeat() {
|
|
39
|
+
try {
|
|
40
|
+
fs.writeFileSync(workerHeartbeatPath, `${Date.now()}\n`, 'utf8');
|
|
41
|
+
} catch (err) {
|
|
42
|
+
// Heartbeat is best-effort and must not crash the worker.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
function isPidRunning(pid) {
|
|
37
47
|
if (!pid) return false;
|
|
38
48
|
try {
|
|
@@ -51,6 +61,7 @@ function getExistingSupervisorPid() {
|
|
|
51
61
|
|
|
52
62
|
function startTelegramInBackground() {
|
|
53
63
|
ensureContextFiles();
|
|
64
|
+
ensureSwarmLayout();
|
|
54
65
|
const existingPid = getExistingSupervisorPid();
|
|
55
66
|
if (existingPid && isPidRunning(existingPid)) {
|
|
56
67
|
process.stdout.write(`Telegram background bot is already running (PID ${existingPid}).\n`);
|
|
@@ -85,7 +96,9 @@ function stopTelegramBackground() {
|
|
|
85
96
|
process.kill(pid, 'SIGTERM');
|
|
86
97
|
fs.unlinkSync(supervisorPidPath);
|
|
87
98
|
const workerPidPath = path.resolve(rootDir, 'tiger-telegram-worker.pid');
|
|
99
|
+
const workerHeartbeatPath = path.resolve(rootDir, 'tiger-telegram-worker.heartbeat');
|
|
88
100
|
if (fs.existsSync(workerPidPath)) fs.unlinkSync(workerPidPath);
|
|
101
|
+
if (fs.existsSync(workerHeartbeatPath)) fs.unlinkSync(workerHeartbeatPath);
|
|
89
102
|
process.stdout.write(`Stopped Telegram background bot (supervisor PID ${pid}).\n`);
|
|
90
103
|
}
|
|
91
104
|
|
|
@@ -113,6 +126,7 @@ function printVectorMemoryStatus(vectorStatus) {
|
|
|
113
126
|
|
|
114
127
|
async function runCli() {
|
|
115
128
|
ensureContextFiles();
|
|
129
|
+
ensureSwarmLayout();
|
|
116
130
|
startReflectionScheduler();
|
|
117
131
|
const vectorStatus = initVectorMemory();
|
|
118
132
|
printVectorMemoryStatus(vectorStatus);
|
|
@@ -159,6 +173,7 @@ async function runCli() {
|
|
|
159
173
|
|
|
160
174
|
async function main() {
|
|
161
175
|
const argv = process.argv.slice(2);
|
|
176
|
+
const isWorkerProcess = hasFlag(argv, '--worker');
|
|
162
177
|
if (hasFlag(argv, '--telegram-stop')) {
|
|
163
178
|
stopTelegramBackground();
|
|
164
179
|
return;
|
|
@@ -171,9 +186,15 @@ async function main() {
|
|
|
171
186
|
|
|
172
187
|
if (isTelegramMode(argv)) {
|
|
173
188
|
ensureContextFiles();
|
|
189
|
+
ensureSwarmLayout();
|
|
174
190
|
startReflectionScheduler();
|
|
175
191
|
const vectorStatus = initVectorMemory();
|
|
176
192
|
printVectorMemoryStatus(vectorStatus);
|
|
193
|
+
if (isWorkerProcess) {
|
|
194
|
+
// NanoClaw-style heartbeat: worker emits liveness every minute.
|
|
195
|
+
writeWorkerHeartbeat();
|
|
196
|
+
setInterval(writeWorkerHeartbeat, 60 * 1000);
|
|
197
|
+
}
|
|
177
198
|
startTelegramBot();
|
|
178
199
|
process.stdout.write('Telegram bot started.\n');
|
|
179
200
|
return;
|