voyageai-cli 1.26.0 → 1.27.0
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/package.json +1 -1
- package/src/commands/chat.js +281 -78
- package/src/commands/doctor.js +157 -14
- package/src/commands/playground.js +233 -19
- package/src/lib/chat.js +170 -4
- package/src/lib/llm.js +304 -2
- package/src/lib/mongo.js +6 -6
- package/src/lib/prompt.js +60 -1
- package/src/lib/tool-registry.js +194 -0
- package/src/mcp/tools/embedding.js +55 -43
- package/src/mcp/tools/ingest.js +74 -67
- package/src/mcp/tools/management.js +60 -48
- package/src/mcp/tools/retrieval.js +181 -163
- package/src/mcp/tools/utility.js +171 -153
- package/src/playground/icons/dark/128.png +0 -0
- package/src/playground/icons/dark/16.png +0 -0
- package/src/playground/icons/dark/256.png +0 -0
- package/src/playground/icons/dark/32.png +0 -0
- package/src/playground/icons/dark/64.png +0 -0
- package/src/playground/icons/light/128.png +0 -0
- package/src/playground/icons/light/16.png +0 -0
- package/src/playground/icons/light/256.png +0 -0
- package/src/playground/icons/light/32.png +0 -0
- package/src/playground/icons/light/64.png +0 -0
- package/src/playground/index.html +2769 -27
package/package.json
CHANGED
package/src/commands/chat.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const readline = require('readline');
|
|
4
4
|
const { createLLMProvider, resolveLLMConfig } = require('../lib/llm');
|
|
5
5
|
const { ChatHistory } = require('../lib/history');
|
|
6
|
-
const { chatTurn } = require('../lib/chat');
|
|
6
|
+
const { chatTurn, agentChatTurn } = require('../lib/chat');
|
|
7
7
|
const { loadProject } = require('../lib/project');
|
|
8
8
|
const { getMongoCollection } = require('../lib/mongo');
|
|
9
9
|
const { setConfigValue } = require('../lib/config');
|
|
@@ -29,6 +29,7 @@ function registerChat(program) {
|
|
|
29
29
|
.option('--llm-model <name>', 'Specific LLM model to use')
|
|
30
30
|
.option('--llm-api-key <key>', 'LLM API key')
|
|
31
31
|
.option('--llm-base-url <url>', 'LLM API base URL (Ollama)')
|
|
32
|
+
.option('--mode <mode>', 'Chat mode: pipeline (fixed RAG) or agent (tool-calling)', 'pipeline')
|
|
32
33
|
.option('--max-context-docs <n>', 'Max retrieved documents for context', (v) => parseInt(v, 10), 5)
|
|
33
34
|
.option('--max-turns <n>', 'Max conversation turns before truncation', (v) => parseInt(v, 10), 20)
|
|
34
35
|
.option('--no-history', 'Disable MongoDB persistence (in-memory only)')
|
|
@@ -63,13 +64,19 @@ async function runChat(opts) {
|
|
|
63
64
|
const doStream = opts.stream !== false;
|
|
64
65
|
const systemPrompt = opts.systemPrompt || chatConf.systemPrompt;
|
|
65
66
|
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
// Resolve mode
|
|
68
|
+
const mode = opts.mode || chatConf.mode || 'pipeline';
|
|
69
|
+
const isAgent = mode === 'agent';
|
|
70
|
+
|
|
71
|
+
// Validate DB + collection (required for pipeline, recommended for agent)
|
|
72
|
+
if (!isAgent && (!db || !collection)) {
|
|
73
|
+
console.error(ui.error('Database and collection required for pipeline mode.'));
|
|
69
74
|
console.error('');
|
|
70
75
|
console.error(' Use --db and --collection, or configure .vai.json:');
|
|
71
76
|
console.error(' vai init');
|
|
72
77
|
console.error('');
|
|
78
|
+
console.error(' Or use --mode agent to let the LLM discover collections.');
|
|
79
|
+
console.error('');
|
|
73
80
|
process.exit(1);
|
|
74
81
|
}
|
|
75
82
|
|
|
@@ -131,8 +138,17 @@ async function runChat(opts) {
|
|
|
131
138
|
|
|
132
139
|
const llm = createLLMProvider(opts);
|
|
133
140
|
|
|
134
|
-
//
|
|
135
|
-
if (!
|
|
141
|
+
// Check tool support for agent mode
|
|
142
|
+
if (isAgent && !llm.supportsTools) {
|
|
143
|
+
if (!opts.quiet && !opts.json) {
|
|
144
|
+
console.log(ui.warn(`LLM provider "${llm.name}" does not support tool calling. Falling back to pipeline mode.`));
|
|
145
|
+
}
|
|
146
|
+
// Fall through to pipeline mode
|
|
147
|
+
return runChat({ ...opts, mode: 'pipeline' });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Preflight: verify the RAG pipeline is ready (pipeline mode only)
|
|
151
|
+
if (!isAgent && !opts.json) {
|
|
136
152
|
const { runPreflight, formatPreflight, waitForIndex } = require('../lib/preflight');
|
|
137
153
|
const { checks, ready } = await runPreflight({
|
|
138
154
|
db, collection,
|
|
@@ -184,7 +200,7 @@ async function runChat(opts) {
|
|
|
184
200
|
try {
|
|
185
201
|
const historyCollection = chatConf.historyCollection ||
|
|
186
202
|
process.env.VAI_CHAT_HISTORY || 'vai_chat_history';
|
|
187
|
-
const { client, collection: coll } = await getMongoCollection(db, historyCollection);
|
|
203
|
+
const { client, collection: coll } = await getMongoCollection(db || 'vai', historyCollection);
|
|
188
204
|
historyMongo = { client, collection: coll };
|
|
189
205
|
await ChatHistory.ensureIndexes(coll);
|
|
190
206
|
} catch {
|
|
@@ -207,12 +223,22 @@ async function runChat(opts) {
|
|
|
207
223
|
}
|
|
208
224
|
}
|
|
209
225
|
|
|
226
|
+
// Track tool calls from last agent response (for /tools and /export-workflow)
|
|
227
|
+
let lastToolCalls = [];
|
|
228
|
+
|
|
210
229
|
// Print header
|
|
211
230
|
if (!opts.quiet && !opts.json) {
|
|
212
231
|
console.log('');
|
|
213
232
|
console.log(`${pc.bold('vai chat')} v${getVersion()}`);
|
|
214
233
|
console.log(ui.label('Provider', `${llmConfig.provider} (${llmConfig.model})`));
|
|
215
|
-
|
|
234
|
+
if (isAgent) {
|
|
235
|
+
console.log(ui.label('Mode', 'agent (tool-calling)'));
|
|
236
|
+
if (db) console.log(ui.label('Default DB', db));
|
|
237
|
+
if (collection) console.log(ui.label('Default collection', collection));
|
|
238
|
+
} else {
|
|
239
|
+
console.log(ui.label('Mode', 'pipeline (fixed RAG)'));
|
|
240
|
+
console.log(ui.label('Knowledge base', `${db}.${collection}`));
|
|
241
|
+
}
|
|
216
242
|
console.log(ui.label('Session', pc.dim(history.sessionId)));
|
|
217
243
|
console.log(pc.dim('Type /help for commands, /quit to exit.'));
|
|
218
244
|
console.log('');
|
|
@@ -237,7 +263,10 @@ async function runChat(opts) {
|
|
|
237
263
|
|
|
238
264
|
// Handle slash commands
|
|
239
265
|
if (input.startsWith('/')) {
|
|
240
|
-
const handled = await handleSlashCommand(input, {
|
|
266
|
+
const handled = await handleSlashCommand(input, {
|
|
267
|
+
history, opts, db, collection, llm, rl, historyMongo,
|
|
268
|
+
isAgent, lastToolCalls,
|
|
269
|
+
});
|
|
241
270
|
if (handled === 'quit') {
|
|
242
271
|
await cleanup(historyMongo);
|
|
243
272
|
process.exit(0);
|
|
@@ -248,70 +277,15 @@ async function runChat(opts) {
|
|
|
248
277
|
|
|
249
278
|
// Execute chat turn
|
|
250
279
|
try {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
let fullResponse = '';
|
|
256
|
-
let sources = [];
|
|
257
|
-
let metadata = {};
|
|
258
|
-
|
|
259
|
-
for await (const event of chatTurn({
|
|
260
|
-
query: input, db, collection, llm, history,
|
|
261
|
-
opts: { maxDocs, rerank: doRerank, stream: false, systemPrompt, textField, filter: opts.filter },
|
|
262
|
-
})) {
|
|
263
|
-
if (event.type === 'chunk') fullResponse += event.data;
|
|
264
|
-
if (event.type === 'done') {
|
|
265
|
-
sources = event.data.sources;
|
|
266
|
-
metadata = event.data.metadata;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
console.log(JSON.stringify({
|
|
271
|
-
sessionId: history.sessionId,
|
|
272
|
-
turn: turnNum,
|
|
273
|
-
query: input,
|
|
274
|
-
response: fullResponse,
|
|
275
|
-
sources,
|
|
276
|
-
metadata,
|
|
277
|
-
}));
|
|
280
|
+
if (isAgent) {
|
|
281
|
+
lastToolCalls = await handleAgentTurn(input, {
|
|
282
|
+
llm, history, opts, db, collection, systemPrompt, chatConf,
|
|
283
|
+
});
|
|
278
284
|
} else {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
query: input, db, collection, llm, history,
|
|
284
|
-
opts: { maxDocs, rerank: doRerank, stream: doStream, systemPrompt, textField, filter: opts.filter },
|
|
285
|
-
})) {
|
|
286
|
-
if (event.type === 'retrieval' && !opts.quiet) {
|
|
287
|
-
const { docs, timeMs } = event.data;
|
|
288
|
-
if (!retrievalShown) {
|
|
289
|
-
console.log(pc.dim(` [${docs.length} docs retrieved in ${timeMs}ms]`));
|
|
290
|
-
console.log('');
|
|
291
|
-
retrievalShown = true;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (event.type === 'chunk') {
|
|
296
|
-
process.stdout.write(event.data);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (event.type === 'done') {
|
|
300
|
-
console.log(''); // End the streamed response line
|
|
301
|
-
|
|
302
|
-
// Show sources
|
|
303
|
-
const { sources, metadata } = event.data;
|
|
304
|
-
if (sources.length > 0 && chatConf.showSources !== false) {
|
|
305
|
-
console.log('');
|
|
306
|
-
console.log(pc.dim('Sources:'));
|
|
307
|
-
for (let i = 0; i < sources.length; i++) {
|
|
308
|
-
const s = sources[i];
|
|
309
|
-
console.log(pc.dim(` [${i + 1}] ${s.source} (relevance: ${s.score?.toFixed(2) || 'N/A'})`));
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
console.log('');
|
|
313
|
-
}
|
|
314
|
-
}
|
|
285
|
+
await handlePipelineTurn(input, {
|
|
286
|
+
db, collection, llm, history, opts,
|
|
287
|
+
maxDocs, doRerank, doStream, systemPrompt, textField, chatConf,
|
|
288
|
+
});
|
|
315
289
|
}
|
|
316
290
|
} catch (err) {
|
|
317
291
|
console.error('');
|
|
@@ -335,12 +309,160 @@ async function runChat(opts) {
|
|
|
335
309
|
});
|
|
336
310
|
}
|
|
337
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Handle a single pipeline mode turn.
|
|
314
|
+
*/
|
|
315
|
+
async function handlePipelineTurn(input, ctx) {
|
|
316
|
+
const { db, collection, llm, history, opts, maxDocs, doRerank, doStream, systemPrompt, textField, chatConf } = ctx;
|
|
317
|
+
const turnNum = Math.floor(history.turns.length / 2) + 1;
|
|
318
|
+
|
|
319
|
+
if (opts.json) {
|
|
320
|
+
// JSON mode — collect everything then output
|
|
321
|
+
let fullResponse = '';
|
|
322
|
+
let sources = [];
|
|
323
|
+
let metadata = {};
|
|
324
|
+
|
|
325
|
+
for await (const event of chatTurn({
|
|
326
|
+
query: input, db, collection, llm, history,
|
|
327
|
+
opts: { maxDocs, rerank: doRerank, stream: false, systemPrompt, textField, filter: opts.filter },
|
|
328
|
+
})) {
|
|
329
|
+
if (event.type === 'chunk') fullResponse += event.data;
|
|
330
|
+
if (event.type === 'done') {
|
|
331
|
+
sources = event.data.sources;
|
|
332
|
+
metadata = event.data.metadata;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
console.log(JSON.stringify({
|
|
337
|
+
sessionId: history.sessionId,
|
|
338
|
+
turn: turnNum,
|
|
339
|
+
query: input,
|
|
340
|
+
response: fullResponse,
|
|
341
|
+
sources,
|
|
342
|
+
metadata,
|
|
343
|
+
}));
|
|
344
|
+
} else {
|
|
345
|
+
// Interactive mode — stream output
|
|
346
|
+
let retrievalShown = false;
|
|
347
|
+
|
|
348
|
+
for await (const event of chatTurn({
|
|
349
|
+
query: input, db, collection, llm, history,
|
|
350
|
+
opts: { maxDocs, rerank: doRerank, stream: doStream, systemPrompt, textField, filter: opts.filter },
|
|
351
|
+
})) {
|
|
352
|
+
if (event.type === 'retrieval' && !opts.quiet) {
|
|
353
|
+
const { docs, timeMs } = event.data;
|
|
354
|
+
if (!retrievalShown) {
|
|
355
|
+
console.log(pc.dim(` [${docs.length} docs retrieved in ${timeMs}ms]`));
|
|
356
|
+
console.log('');
|
|
357
|
+
retrievalShown = true;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (event.type === 'chunk') {
|
|
362
|
+
process.stdout.write(event.data);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (event.type === 'done') {
|
|
366
|
+
console.log(''); // End the streamed response line
|
|
367
|
+
|
|
368
|
+
// Show sources
|
|
369
|
+
const { sources, metadata } = event.data;
|
|
370
|
+
if (sources.length > 0 && chatConf.showSources !== false) {
|
|
371
|
+
console.log('');
|
|
372
|
+
console.log(pc.dim('Sources:'));
|
|
373
|
+
for (let i = 0; i < sources.length; i++) {
|
|
374
|
+
const s = sources[i];
|
|
375
|
+
console.log(pc.dim(` [${i + 1}] ${s.source} (relevance: ${s.score?.toFixed(2) || 'N/A'})`));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
console.log('');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Handle a single agent mode turn.
|
|
386
|
+
* @returns {Array} Tool calls from this turn (for /tools and /export-workflow)
|
|
387
|
+
*/
|
|
388
|
+
async function handleAgentTurn(input, ctx) {
|
|
389
|
+
const { llm, history, opts, db, collection, systemPrompt, chatConf } = ctx;
|
|
390
|
+
const showToolCalls = chatConf.showToolCalls !== undefined ? chatConf.showToolCalls : true;
|
|
391
|
+
const toolCalls = [];
|
|
392
|
+
|
|
393
|
+
if (opts.json) {
|
|
394
|
+
// JSON mode — collect everything then output
|
|
395
|
+
let fullResponse = '';
|
|
396
|
+
let metadata = {};
|
|
397
|
+
|
|
398
|
+
for await (const event of agentChatTurn({
|
|
399
|
+
query: input, llm, history,
|
|
400
|
+
opts: { systemPrompt, db, collection },
|
|
401
|
+
})) {
|
|
402
|
+
if (event.type === 'tool_call') {
|
|
403
|
+
toolCalls.push(event.data);
|
|
404
|
+
}
|
|
405
|
+
if (event.type === 'chunk') fullResponse += event.data;
|
|
406
|
+
if (event.type === 'done') {
|
|
407
|
+
metadata = event.data.metadata;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
console.log(JSON.stringify({
|
|
412
|
+
sessionId: history.sessionId,
|
|
413
|
+
query: input,
|
|
414
|
+
response: fullResponse,
|
|
415
|
+
toolCalls,
|
|
416
|
+
metadata,
|
|
417
|
+
}));
|
|
418
|
+
} else {
|
|
419
|
+
// Interactive mode
|
|
420
|
+
for await (const event of agentChatTurn({
|
|
421
|
+
query: input, llm, history,
|
|
422
|
+
opts: { systemPrompt, db, collection },
|
|
423
|
+
})) {
|
|
424
|
+
if (event.type === 'tool_call') {
|
|
425
|
+
toolCalls.push(event.data);
|
|
426
|
+
if (showToolCalls) {
|
|
427
|
+
const { name, timeMs, error } = event.data;
|
|
428
|
+
if (error) {
|
|
429
|
+
console.log(pc.dim(` [tool] ${name} ${pc.red('failed')} (${timeMs}ms): ${error}`));
|
|
430
|
+
} else if (showToolCalls === 'verbose') {
|
|
431
|
+
console.log(pc.dim(` [tool] ${name} (${timeMs}ms)`));
|
|
432
|
+
const result = event.data.result;
|
|
433
|
+
if (result) {
|
|
434
|
+
const preview = JSON.stringify(result).substring(0, 200);
|
|
435
|
+
console.log(pc.dim(` ${preview}${JSON.stringify(result).length > 200 ? '...' : ''}`));
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
console.log(pc.dim(` [tool] ${name} (${timeMs}ms)`));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (event.type === 'chunk') {
|
|
444
|
+
if (toolCalls.length > 0 && !opts.quiet) {
|
|
445
|
+
console.log(''); // Visual separator after tool calls
|
|
446
|
+
}
|
|
447
|
+
process.stdout.write(event.data);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (event.type === 'done') {
|
|
451
|
+
console.log(''); // End the response line
|
|
452
|
+
console.log('');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return toolCalls;
|
|
458
|
+
}
|
|
459
|
+
|
|
338
460
|
/**
|
|
339
461
|
* Handle slash commands within the REPL.
|
|
340
462
|
* @returns {'quit'|true|false} - 'quit' to exit, true if handled, false if unknown
|
|
341
463
|
*/
|
|
342
464
|
async function handleSlashCommand(input, ctx) {
|
|
343
|
-
const { history, opts, db, collection, llm, rl } = ctx;
|
|
465
|
+
const { history, opts, db, collection, llm, rl, isAgent, lastToolCalls } = ctx;
|
|
344
466
|
const parts = input.split(/\s+/);
|
|
345
467
|
const cmd = parts[0].toLowerCase();
|
|
346
468
|
|
|
@@ -360,6 +482,10 @@ async function handleSlashCommand(input, ctx) {
|
|
|
360
482
|
console.log(' /clear Clear conversation history');
|
|
361
483
|
console.log(' /model Show or switch LLM model (/model <name>)');
|
|
362
484
|
console.log(' /export Export conversation (markdown or json)');
|
|
485
|
+
if (isAgent) {
|
|
486
|
+
console.log(' /tools Show tool calls from last response');
|
|
487
|
+
console.log(' /export-workflow Export last tool sequence as workflow');
|
|
488
|
+
}
|
|
363
489
|
console.log(' /help Show this help');
|
|
364
490
|
console.log(' /quit Exit chat');
|
|
365
491
|
console.log('');
|
|
@@ -382,15 +508,20 @@ async function handleSlashCommand(input, ctx) {
|
|
|
382
508
|
case '/session':
|
|
383
509
|
console.log(` Session: ${history.sessionId}`);
|
|
384
510
|
console.log(` Turns: ${Math.floor(history.turns.length / 2)}`);
|
|
511
|
+
if (isAgent) {
|
|
512
|
+
console.log(` Mode: agent (tool-calling)`);
|
|
513
|
+
} else {
|
|
514
|
+
console.log(` Mode: pipeline (fixed RAG)`);
|
|
515
|
+
}
|
|
385
516
|
return true;
|
|
386
517
|
|
|
387
518
|
case '/context': {
|
|
388
|
-
const
|
|
389
|
-
if (!
|
|
519
|
+
const lastCtx = history.getLastContext();
|
|
520
|
+
if (!lastCtx) {
|
|
390
521
|
console.log(pc.dim(' No context available yet.'));
|
|
391
522
|
} else {
|
|
392
523
|
console.log('');
|
|
393
|
-
for (const doc of
|
|
524
|
+
for (const doc of lastCtx) {
|
|
394
525
|
console.log(pc.bold(` [${doc.source}]`));
|
|
395
526
|
const preview = (doc.text || '').substring(0, 300);
|
|
396
527
|
console.log(` ${preview}${doc.text?.length > 300 ? '...' : ''}`);
|
|
@@ -413,7 +544,7 @@ async function handleSlashCommand(input, ctx) {
|
|
|
413
544
|
} else {
|
|
414
545
|
console.log('');
|
|
415
546
|
for (const s of sessions) {
|
|
416
|
-
const active = s.sessionId === history.sessionId ? pc.green('
|
|
547
|
+
const active = s.sessionId === history.sessionId ? pc.green(' <- current') : '';
|
|
417
548
|
const date = s.lastActivity ? new Date(s.lastActivity).toLocaleString() : 'unknown';
|
|
418
549
|
const preview = (s.firstMessage || '').substring(0, 60);
|
|
419
550
|
console.log(` ${pc.bold(s.sessionId.slice(0, 8))} ${pc.dim(date)} ${s.turnCount} turns${active}`);
|
|
@@ -448,7 +579,7 @@ async function handleSlashCommand(input, ctx) {
|
|
|
448
579
|
console.log('');
|
|
449
580
|
console.log(` Available models:`);
|
|
450
581
|
for (const m of models) {
|
|
451
|
-
const current = m.id === llm.model ? pc.green('
|
|
582
|
+
const current = m.id === llm.model ? pc.green(' <- current') : '';
|
|
452
583
|
let info = m.name || m.id;
|
|
453
584
|
if (m.size) info += pc.dim(` (${m.size})`);
|
|
454
585
|
if (m.parameterSize) info += pc.dim(` [${m.parameterSize}]`);
|
|
@@ -479,6 +610,78 @@ async function handleSlashCommand(input, ctx) {
|
|
|
479
610
|
return true;
|
|
480
611
|
}
|
|
481
612
|
|
|
613
|
+
case '/tools': {
|
|
614
|
+
if (!isAgent) {
|
|
615
|
+
console.log(pc.dim(' /tools is only available in agent mode (--mode agent).'));
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
if (!lastToolCalls || lastToolCalls.length === 0) {
|
|
619
|
+
console.log(pc.dim(' No tool calls from the last response.'));
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
console.log('');
|
|
623
|
+
console.log(pc.bold(` Tool calls (${lastToolCalls.length}):`));
|
|
624
|
+
console.log('');
|
|
625
|
+
for (let i = 0; i < lastToolCalls.length; i++) {
|
|
626
|
+
const tc = lastToolCalls[i];
|
|
627
|
+
const status = tc.error ? pc.red('FAILED') : pc.green('OK');
|
|
628
|
+
console.log(` ${i + 1}. ${pc.bold(tc.name)} [${status}] (${tc.timeMs}ms)`);
|
|
629
|
+
|
|
630
|
+
// Show args
|
|
631
|
+
const argKeys = Object.keys(tc.args || {});
|
|
632
|
+
if (argKeys.length > 0) {
|
|
633
|
+
const argStr = argKeys.map(k => `${k}=${JSON.stringify(tc.args[k])}`).join(', ');
|
|
634
|
+
const preview = argStr.substring(0, 120);
|
|
635
|
+
console.log(pc.dim(` Args: ${preview}${argStr.length > 120 ? '...' : ''}`));
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Show result summary
|
|
639
|
+
if (tc.error) {
|
|
640
|
+
console.log(pc.dim(` Error: ${tc.error}`));
|
|
641
|
+
} else if (tc.result) {
|
|
642
|
+
const resultStr = JSON.stringify(tc.result);
|
|
643
|
+
const preview = resultStr.substring(0, 120);
|
|
644
|
+
console.log(pc.dim(` Result: ${preview}${resultStr.length > 120 ? '...' : ''}`));
|
|
645
|
+
}
|
|
646
|
+
console.log('');
|
|
647
|
+
}
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
case '/export-workflow': {
|
|
652
|
+
if (!isAgent) {
|
|
653
|
+
console.log(pc.dim(' /export-workflow is only available in agent mode (--mode agent).'));
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
if (!lastToolCalls || lastToolCalls.length === 0) {
|
|
657
|
+
console.log(pc.dim(' No tool calls to export. Ask a question first.'));
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const workflow = {
|
|
662
|
+
name: `agent-workflow-${Date.now()}`,
|
|
663
|
+
description: 'Workflow exported from vai chat agent session',
|
|
664
|
+
version: '1.0.0',
|
|
665
|
+
steps: lastToolCalls.map((tc, i) => ({
|
|
666
|
+
id: `step_${i + 1}`,
|
|
667
|
+
tool: tc.name,
|
|
668
|
+
args: tc.args,
|
|
669
|
+
description: `Step ${i + 1}: ${tc.name}`,
|
|
670
|
+
})),
|
|
671
|
+
metadata: {
|
|
672
|
+
exportedAt: new Date().toISOString(),
|
|
673
|
+
sessionId: history.sessionId,
|
|
674
|
+
llmProvider: llm.name,
|
|
675
|
+
llmModel: llm.model,
|
|
676
|
+
},
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
const filename = `agent-workflow-${history.sessionId.slice(0, 8)}.vai-workflow.json`;
|
|
680
|
+
fs.writeFileSync(filename, JSON.stringify(workflow, null, 2) + '\n');
|
|
681
|
+
console.log(ui.success(`Exported ${lastToolCalls.length} tool calls to ${filename}`));
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
|
|
482
685
|
default:
|
|
483
686
|
console.log(pc.dim(` Unknown command: ${cmd}. Type /help for available commands.`));
|
|
484
687
|
return true;
|