voyageai-cli 1.26.1 → 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/doctor.js +157 -14
- package/src/commands/playground.js +193 -2
- 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 +2301 -57
package/package.json
CHANGED
package/src/commands/doctor.js
CHANGED
|
@@ -4,7 +4,7 @@ const os = require('os');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const pc = require('picocolors');
|
|
7
|
-
const { getConfigValue } = require('../lib/config');
|
|
7
|
+
const { getConfigValue, setConfigValue, CONFIG_DIR, CONFIG_PATH } = require('../lib/config');
|
|
8
8
|
const { getApiBase } = require('../lib/api');
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -240,15 +240,83 @@ async function checkConfig() {
|
|
|
240
240
|
};
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
// ── Fix functions (for --fix mode) ──
|
|
244
|
+
|
|
245
|
+
async function fixApiKey() {
|
|
246
|
+
const readline = require('readline');
|
|
247
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
248
|
+
|
|
249
|
+
return new Promise((resolve) => {
|
|
250
|
+
console.log(pc.cyan('\n Fixing: Voyage AI API Key'));
|
|
251
|
+
console.log(pc.dim(' Get a key at: https://dash.voyageai.com/api-keys\n'));
|
|
252
|
+
|
|
253
|
+
rl.question(' Enter your Voyage AI API key: ', (key) => {
|
|
254
|
+
rl.close();
|
|
255
|
+
const trimmed = (key || '').trim();
|
|
256
|
+
if (!trimmed) {
|
|
257
|
+
console.log(pc.yellow(' Skipped (no key entered)'));
|
|
258
|
+
resolve(false);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
setConfigValue('apiKey', trimmed);
|
|
263
|
+
// Also set in env for the current session so the connection check passes
|
|
264
|
+
process.env.VOYAGE_API_KEY = trimmed;
|
|
265
|
+
console.log(pc.green(' ✓ API key saved to ~/.vai/config.json'));
|
|
266
|
+
resolve(true);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
console.log(pc.red(` ✗ Failed to save: ${err.message}`));
|
|
269
|
+
resolve(false);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function fixConfigPermissions() {
|
|
276
|
+
try {
|
|
277
|
+
fs.chmodSync(CONFIG_PATH, 0o600);
|
|
278
|
+
console.log(pc.green(' ✓ Fixed ~/.vai/config.json permissions to 600'));
|
|
279
|
+
return true;
|
|
280
|
+
} catch (err) {
|
|
281
|
+
console.log(pc.red(` ✗ Failed to fix permissions: ${err.message}`));
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function fixConfigDir() {
|
|
287
|
+
try {
|
|
288
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
289
|
+
console.log(pc.green(' ✓ Created ~/.vai/ directory'));
|
|
290
|
+
return true;
|
|
291
|
+
} catch (err) {
|
|
292
|
+
console.log(pc.red(` ✗ Failed to create directory: ${err.message}`));
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function fixPdfParse() {
|
|
298
|
+
const { execSync } = require('child_process');
|
|
299
|
+
console.log(pc.cyan('\n Installing pdf-parse...'));
|
|
300
|
+
try {
|
|
301
|
+
execSync('npm install pdf-parse', { stdio: 'pipe' });
|
|
302
|
+
console.log(pc.green(' ✓ pdf-parse installed'));
|
|
303
|
+
return true;
|
|
304
|
+
} catch (err) {
|
|
305
|
+
console.log(pc.red(` ✗ Failed to install: ${err.message}`));
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
243
310
|
async function runDoctor(options = {}) {
|
|
244
|
-
const { json, verbose } = options;
|
|
245
|
-
|
|
311
|
+
const { json, verbose, fix } = options;
|
|
312
|
+
|
|
246
313
|
console.log(pc.bold('\n🩺 Voyage AI CLI Health Check\n'));
|
|
247
|
-
|
|
314
|
+
|
|
248
315
|
const results = {};
|
|
249
316
|
let hasError = false;
|
|
250
317
|
let hasWarning = false;
|
|
251
|
-
|
|
318
|
+
const fixable = [];
|
|
319
|
+
|
|
252
320
|
// Run all checks
|
|
253
321
|
const checks = [
|
|
254
322
|
{ key: 'node', fn: checkNodeVersion },
|
|
@@ -258,12 +326,12 @@ async function runDoctor(options = {}) {
|
|
|
258
326
|
{ key: 'pdfParse', fn: checkPdfParse },
|
|
259
327
|
{ key: 'config', fn: checkConfig },
|
|
260
328
|
];
|
|
261
|
-
|
|
329
|
+
|
|
262
330
|
for (const { key, fn } of checks) {
|
|
263
331
|
const check = CHECKS[key];
|
|
264
332
|
const result = await fn();
|
|
265
333
|
results[key] = result;
|
|
266
|
-
|
|
334
|
+
|
|
267
335
|
// Determine status icon
|
|
268
336
|
let icon;
|
|
269
337
|
if (result.ok === true) {
|
|
@@ -276,15 +344,23 @@ async function runDoctor(options = {}) {
|
|
|
276
344
|
icon = warnMark();
|
|
277
345
|
hasWarning = true;
|
|
278
346
|
}
|
|
279
|
-
|
|
347
|
+
|
|
280
348
|
// Print result
|
|
281
349
|
console.log(` ${icon} ${pc.bold(check.name)}: ${result.message}`);
|
|
282
|
-
|
|
350
|
+
|
|
283
351
|
if (result.hint && (verbose || result.ok === false)) {
|
|
284
352
|
console.log(` ${pc.dim(result.hint)}`);
|
|
285
353
|
}
|
|
354
|
+
|
|
355
|
+
// Track fixable issues
|
|
356
|
+
if (result.ok !== true) {
|
|
357
|
+
if (key === 'apiKey' && result.ok === false) fixable.push('apiKey');
|
|
358
|
+
if (key === 'pdfParse' && result.ok === null) fixable.push('pdfParse');
|
|
359
|
+
if (key === 'config' && result.message && result.message.includes('permissions')) fixable.push('configPerms');
|
|
360
|
+
if (key === 'config' && result.ok === null) fixable.push('configDir');
|
|
361
|
+
}
|
|
286
362
|
}
|
|
287
|
-
|
|
363
|
+
|
|
288
364
|
// Summary
|
|
289
365
|
console.log('');
|
|
290
366
|
if (hasError) {
|
|
@@ -294,7 +370,43 @@ async function runDoctor(options = {}) {
|
|
|
294
370
|
} else {
|
|
295
371
|
console.log(pc.green(' ✓ All checks passed. vai is ready to use!\n'));
|
|
296
372
|
}
|
|
297
|
-
|
|
373
|
+
|
|
374
|
+
// --fix mode: attempt automatic repairs
|
|
375
|
+
if (fix && fixable.length > 0) {
|
|
376
|
+
console.log(pc.bold(' 🔧 Attempting fixes...\n'));
|
|
377
|
+
let fixed = 0;
|
|
378
|
+
|
|
379
|
+
for (const item of fixable) {
|
|
380
|
+
if (item === 'configDir') {
|
|
381
|
+
if (fixConfigDir()) fixed++;
|
|
382
|
+
}
|
|
383
|
+
if (item === 'apiKey') {
|
|
384
|
+
if (await fixApiKey()) fixed++;
|
|
385
|
+
}
|
|
386
|
+
if (item === 'configPerms') {
|
|
387
|
+
if (fixConfigPermissions()) fixed++;
|
|
388
|
+
}
|
|
389
|
+
if (item === 'pdfParse') {
|
|
390
|
+
if (await fixPdfParse()) fixed++;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
console.log('');
|
|
395
|
+
if (fixed > 0) {
|
|
396
|
+
console.log(pc.green(` ✓ Fixed ${fixed} issue${fixed === 1 ? '' : 's'}. Run ${pc.bold('vai doctor')} again to verify.\n`));
|
|
397
|
+
} else {
|
|
398
|
+
console.log(pc.yellow(' No issues were fixed. See hints above for manual steps.\n'));
|
|
399
|
+
}
|
|
400
|
+
return 0;
|
|
401
|
+
} else if (fix && fixable.length === 0 && (hasError || hasWarning)) {
|
|
402
|
+
console.log(pc.dim(' No auto-fixable issues found. See hints above for manual steps.\n'));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Suggest --fix if there are fixable issues and not in fix mode
|
|
406
|
+
if (!fix && fixable.length > 0) {
|
|
407
|
+
console.log(pc.dim(` Tip: run ${pc.bold('vai doctor --fix')} to attempt automatic repairs\n`));
|
|
408
|
+
}
|
|
409
|
+
|
|
298
410
|
// Suggest next steps
|
|
299
411
|
if (!hasError) {
|
|
300
412
|
console.log(pc.dim(' Next steps:'));
|
|
@@ -302,11 +414,11 @@ async function runDoctor(options = {}) {
|
|
|
302
414
|
console.log(pc.dim(' vai quickstart — Zero-to-search tutorial'));
|
|
303
415
|
console.log(pc.dim(' vai explain — Learn key concepts\n'));
|
|
304
416
|
}
|
|
305
|
-
|
|
417
|
+
|
|
306
418
|
if (json) {
|
|
307
419
|
console.log(JSON.stringify(results, null, 2));
|
|
308
420
|
}
|
|
309
|
-
|
|
421
|
+
|
|
310
422
|
return hasError ? 1 : 0;
|
|
311
423
|
}
|
|
312
424
|
|
|
@@ -316,10 +428,41 @@ function register(program) {
|
|
|
316
428
|
.description('Run health checks on your vai setup')
|
|
317
429
|
.option('--json', 'Output results as JSON')
|
|
318
430
|
.option('-v, --verbose', 'Show hints for all checks')
|
|
431
|
+
.option('-f, --fix', 'Attempt to fix issues automatically')
|
|
319
432
|
.action(async (options) => {
|
|
320
433
|
const exitCode = await runDoctor(options);
|
|
321
434
|
if (exitCode !== 0) process.exit(exitCode);
|
|
322
435
|
});
|
|
323
436
|
}
|
|
324
437
|
|
|
325
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Run all checks programmatically and return structured results.
|
|
440
|
+
* Used by the playground /api/doctor endpoint.
|
|
441
|
+
*/
|
|
442
|
+
async function runChecks() {
|
|
443
|
+
const checks = [
|
|
444
|
+
{ key: 'node', fn: checkNodeVersion },
|
|
445
|
+
{ key: 'apiKey', fn: checkApiKey },
|
|
446
|
+
{ key: 'apiConnection', fn: checkApiConnection },
|
|
447
|
+
{ key: 'mongodb', fn: checkMongoDB },
|
|
448
|
+
{ key: 'pdfParse', fn: checkPdfParse },
|
|
449
|
+
{ key: 'config', fn: checkConfig },
|
|
450
|
+
];
|
|
451
|
+
|
|
452
|
+
const results = {};
|
|
453
|
+
for (const { key, fn } of checks) {
|
|
454
|
+
const result = await fn();
|
|
455
|
+
// Strip picocolors ANSI codes from messages for JSON output
|
|
456
|
+
const clean = (s) => typeof s === 'string' ? s.replace(/\x1b\[[0-9;]*m/g, '') : s;
|
|
457
|
+
results[key] = {
|
|
458
|
+
name: CHECKS[key].name,
|
|
459
|
+
required: CHECKS[key].required,
|
|
460
|
+
ok: result.ok,
|
|
461
|
+
message: clean(result.message),
|
|
462
|
+
hint: result.hint ? clean(result.hint) : null,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
return results;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
module.exports = { register, runDoctor, runChecks };
|
|
@@ -309,6 +309,20 @@ function createPlaygroundServer() {
|
|
|
309
309
|
return;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
// API: Doctor health checks
|
|
313
|
+
if (req.method === 'GET' && req.url === '/api/doctor') {
|
|
314
|
+
try {
|
|
315
|
+
const { runChecks } = require('./doctor');
|
|
316
|
+
const results = await runChecks();
|
|
317
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
318
|
+
res.end(JSON.stringify(results));
|
|
319
|
+
} catch (err) {
|
|
320
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
321
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
312
326
|
// API: Settings origins — where each config value comes from
|
|
313
327
|
if (req.method === 'GET' && req.url === '/api/settings/origins') {
|
|
314
328
|
const { resolveLLMConfig } = require('../lib/llm');
|
|
@@ -338,6 +352,40 @@ function createPlaygroundServer() {
|
|
|
338
352
|
return;
|
|
339
353
|
}
|
|
340
354
|
|
|
355
|
+
// API: List built-in workflows
|
|
356
|
+
if (req.method === 'GET' && req.url === '/api/workflows') {
|
|
357
|
+
try {
|
|
358
|
+
const { listBuiltinWorkflows } = require('../lib/workflow');
|
|
359
|
+
const workflows = listBuiltinWorkflows();
|
|
360
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
361
|
+
res.end(JSON.stringify({ workflows }));
|
|
362
|
+
} catch (err) {
|
|
363
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
364
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
365
|
+
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// API: Get a specific workflow by name
|
|
370
|
+
if (req.method === 'GET' && req.url?.startsWith('/api/workflows/')) {
|
|
371
|
+
const name = decodeURIComponent(req.url.replace('/api/workflows/', ''));
|
|
372
|
+
if (!name) {
|
|
373
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
374
|
+
res.end(JSON.stringify({ error: 'Workflow name is required' }));
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const { loadWorkflow } = require('../lib/workflow');
|
|
379
|
+
const definition = loadWorkflow(name);
|
|
380
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
381
|
+
res.end(JSON.stringify({ definition }));
|
|
382
|
+
} catch (err) {
|
|
383
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
384
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
385
|
+
}
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
341
389
|
// API: Save chat config (POST) — persists to .vai.json
|
|
342
390
|
// Placed before generic POST handler so it doesn't require Voyage API key
|
|
343
391
|
if (req.method === 'POST' && req.url === '/api/chat/config') {
|
|
@@ -459,8 +507,37 @@ function createPlaygroundServer() {
|
|
|
459
507
|
opts: { systemPrompt, db: db || undefined, collection: collection || undefined },
|
|
460
508
|
})) {
|
|
461
509
|
if (event.type === 'tool_call') {
|
|
462
|
-
const { name, args, timeMs, error } = event.data;
|
|
463
|
-
|
|
510
|
+
const { name, args, timeMs, error, result } = event.data;
|
|
511
|
+
// Build a short human-readable summary of the tool result
|
|
512
|
+
let resultSummary = '';
|
|
513
|
+
if (!error && result) {
|
|
514
|
+
if (name === 'vai_query' || name === 'vai_search') {
|
|
515
|
+
const docs = result.results || result.documents || [];
|
|
516
|
+
resultSummary = `Found ${docs.length} result${docs.length !== 1 ? 's' : ''}`;
|
|
517
|
+
} else if (name === 'vai_collections') {
|
|
518
|
+
const colls = result.collections || [];
|
|
519
|
+
resultSummary = colls.length > 0
|
|
520
|
+
? colls.map(c => `<code>${c.name || c}</code>`).slice(0, 5).join(', ')
|
|
521
|
+
: 'No collections found';
|
|
522
|
+
} else if (name === 'vai_models') {
|
|
523
|
+
const models = result.models || [];
|
|
524
|
+
resultSummary = `${models.length} model${models.length !== 1 ? 's' : ''} available`;
|
|
525
|
+
} else if (name === 'vai_embed') {
|
|
526
|
+
const dims = result.dimensions || result.embedding?.length || '?';
|
|
527
|
+
resultSummary = `${dims}-dim vector`;
|
|
528
|
+
} else if (name === 'vai_similarity') {
|
|
529
|
+
const score = result.similarity ?? result.score;
|
|
530
|
+
resultSummary = score !== undefined ? `Score: ${Number(score).toFixed(4)}` : '';
|
|
531
|
+
} else if (name === 'vai_rerank') {
|
|
532
|
+
const items = result.results || [];
|
|
533
|
+
resultSummary = `Reranked ${items.length} result${items.length !== 1 ? 's' : ''}`;
|
|
534
|
+
} else if (name === 'vai_estimate') {
|
|
535
|
+
resultSummary = result.recommendation || '';
|
|
536
|
+
} else if (name === 'vai_explain' || name === 'vai_topics') {
|
|
537
|
+
resultSummary = result.title || result.topic || '';
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
res.write(`event: tool_call\ndata: ${JSON.stringify({ name, args, timeMs, error, resultSummary })}\n\n`);
|
|
464
541
|
} else if (event.type === 'chunk') {
|
|
465
542
|
res.write(`event: chunk\ndata: ${JSON.stringify({ text: event.data })}\n\n`);
|
|
466
543
|
} else if (event.type === 'done') {
|
|
@@ -645,6 +722,120 @@ function createPlaygroundServer() {
|
|
|
645
722
|
}));
|
|
646
723
|
return;
|
|
647
724
|
}
|
|
725
|
+
|
|
726
|
+
// API: Validate a workflow definition
|
|
727
|
+
if (req.url === '/api/workflows/validate') {
|
|
728
|
+
const { validateWorkflow } = require('../lib/workflow');
|
|
729
|
+
const { definition } = parsed;
|
|
730
|
+
if (!definition) {
|
|
731
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
732
|
+
res.end(JSON.stringify({ error: 'definition is required' }));
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const errors = validateWorkflow(definition);
|
|
736
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
737
|
+
res.end(JSON.stringify({ valid: errors.length === 0, errors }));
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// API: Get execution plan (layers + dependency graph)
|
|
742
|
+
if (req.url === '/api/workflows/plan') {
|
|
743
|
+
const { buildExecutionPlan, buildDependencyGraph, validateWorkflow } = require('../lib/workflow');
|
|
744
|
+
const { definition } = parsed;
|
|
745
|
+
if (!definition || !definition.steps) {
|
|
746
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
747
|
+
res.end(JSON.stringify({ error: 'definition with steps is required' }));
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
const errors = validateWorkflow(definition);
|
|
751
|
+
if (errors.length > 0) {
|
|
752
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
753
|
+
res.end(JSON.stringify({ error: 'Invalid workflow', errors }));
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const layers = buildExecutionPlan(definition.steps);
|
|
757
|
+
const graphMap = buildDependencyGraph(definition.steps);
|
|
758
|
+
// Convert Map<string, Set<string>> to plain object for JSON serialization
|
|
759
|
+
const graph = {};
|
|
760
|
+
for (const [stepId, deps] of graphMap) {
|
|
761
|
+
graph[stepId] = Array.from(deps);
|
|
762
|
+
}
|
|
763
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
764
|
+
res.end(JSON.stringify({ layers, graph }));
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// API: Execute a workflow (streaming SSE)
|
|
769
|
+
if (req.url === '/api/workflows/execute') {
|
|
770
|
+
const { executeWorkflow, validateWorkflow: validateWf } = require('../lib/workflow');
|
|
771
|
+
const { definition, inputs } = parsed;
|
|
772
|
+
if (!definition) {
|
|
773
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
774
|
+
res.end(JSON.stringify({ error: 'definition is required' }));
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
const errors = validateWf(definition);
|
|
778
|
+
if (errors.length > 0) {
|
|
779
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
780
|
+
res.end(JSON.stringify({ error: 'Invalid workflow', errors }));
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Stream execution events as SSE
|
|
785
|
+
res.writeHead(200, {
|
|
786
|
+
'Content-Type': 'text/event-stream',
|
|
787
|
+
'Cache-Control': 'no-cache',
|
|
788
|
+
'Connection': 'keep-alive',
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
try {
|
|
792
|
+
const result = await executeWorkflow(definition, {
|
|
793
|
+
inputs: inputs || {},
|
|
794
|
+
onStepStart: (stepId, stepDef) => {
|
|
795
|
+
res.write(`event: step_start\ndata: ${JSON.stringify({
|
|
796
|
+
stepId,
|
|
797
|
+
name: stepDef.name || stepId,
|
|
798
|
+
tool: stepDef.tool,
|
|
799
|
+
})}\n\n`);
|
|
800
|
+
},
|
|
801
|
+
onStepComplete: (stepId, output, timeMs) => {
|
|
802
|
+
// Summarize output to avoid huge payloads
|
|
803
|
+
let summary = '';
|
|
804
|
+
if (output && typeof output === 'object') {
|
|
805
|
+
if (output.results) summary = `${output.results.length} results`;
|
|
806
|
+
else if (output.similarity !== undefined) summary = `similarity: ${Number(output.similarity).toFixed(4)}`;
|
|
807
|
+
else if (output.text) summary = output.text.slice(0, 100) + (output.text.length > 100 ? '...' : '');
|
|
808
|
+
else summary = JSON.stringify(output).slice(0, 200);
|
|
809
|
+
}
|
|
810
|
+
res.write(`event: step_complete\ndata: ${JSON.stringify({
|
|
811
|
+
stepId, timeMs, summary,
|
|
812
|
+
output: JSON.stringify(output).length < 5000 ? output : { _truncated: true, summary },
|
|
813
|
+
})}\n\n`);
|
|
814
|
+
},
|
|
815
|
+
onStepSkip: (stepId, reason) => {
|
|
816
|
+
res.write(`event: step_skip\ndata: ${JSON.stringify({ stepId, reason })}\n\n`);
|
|
817
|
+
},
|
|
818
|
+
onStepError: (stepId, error) => {
|
|
819
|
+
res.write(`event: step_error\ndata: ${JSON.stringify({
|
|
820
|
+
stepId,
|
|
821
|
+
error: error.message || String(error),
|
|
822
|
+
})}\n\n`);
|
|
823
|
+
},
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
res.write(`event: done\ndata: ${JSON.stringify({
|
|
827
|
+
output: result.output,
|
|
828
|
+
totalTimeMs: result.totalTimeMs,
|
|
829
|
+
layers: result.layers,
|
|
830
|
+
steps: result.steps,
|
|
831
|
+
})}\n\n`);
|
|
832
|
+
} catch (err) {
|
|
833
|
+
res.write(`event: error\ndata: ${JSON.stringify({ error: err.message })}\n\n`);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
res.end();
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
648
839
|
}
|
|
649
840
|
|
|
650
841
|
// 404
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|