minovative-mind-cli 2.10.0 → 2.11.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.
Files changed (34) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/chat.d.ts +1 -1
  3. package/dist/commands/chat.js +2 -1
  4. package/dist/services/agent/commandApproval.js +5 -2
  5. package/dist/services/agent/slashCommands.js +213 -51
  6. package/dist/services/agent-tools.d.ts +4 -3
  7. package/dist/services/agent-tools.js +32 -79
  8. package/dist/services/agent.d.ts +5 -6
  9. package/dist/services/agent.js +38 -15
  10. package/dist/services/ai.d.ts +25 -0
  11. package/dist/services/ai.js +253 -2
  12. package/dist/services/chatHistoryService.d.ts +95 -2
  13. package/dist/services/chatHistoryService.js +236 -9
  14. package/dist/services/contextAgent.js +184 -89
  15. package/dist/services/orchestration/investigationAgent.js +100 -84
  16. package/dist/services/orchestration/investigationOrchestrator.js +6 -2
  17. package/dist/services/orchestration/orchestrator.js +6 -3
  18. package/dist/services/orchestration/scopedTools.js +5 -0
  19. package/dist/services/orchestration/subAgent.d.ts +31 -1
  20. package/dist/services/orchestration/subAgent.js +153 -2
  21. package/dist/services/userProfileService.d.ts +97 -0
  22. package/dist/services/userProfileService.js +410 -0
  23. package/dist/utils/analysisRunner.d.ts +29 -0
  24. package/dist/utils/analysisRunner.js +200 -5
  25. package/dist/utils/contextPrompts.d.ts +19 -3
  26. package/dist/utils/contextPrompts.js +144 -26
  27. package/dist/utils/historyPrompt.d.ts +92 -1
  28. package/dist/utils/historyPrompt.js +166 -2
  29. package/dist/utils/symbolExtractor.d.ts +12 -0
  30. package/dist/utils/symbolExtractor.js +946 -0
  31. package/dist/utils/systemPrompts.d.ts +6 -4
  32. package/dist/utils/systemPrompts.js +77 -9
  33. package/oclif.manifest.json +2 -2
  34. package/package.json +1 -1
@@ -9,7 +9,48 @@ import { debugLog } from '../utils/logger.js';
9
9
  import { buildDependencyGraph } from '../utils/dependencyTracer.js';
10
10
  import { runEphemeralScript } from '../utils/analysisRunner.js';
11
11
  import { getMetricCollector } from './metrics.js';
12
+ import { estimateTokenCount, pruneTextToTokenBudget } from '../utils/historyPrompt.js';
12
13
  const metrics = getMetricCollector();
14
+ /** Token budget constants for context optimization */
15
+ const MAX_TREE_TOKENS = 12000;
16
+ const MAX_CHAT_HISTORY_ROUTER_TOKENS = 3000;
17
+ const MAX_CHAT_HISTORY_INVESTIGATION_TOKENS = 4500;
18
+ const MAX_SINGLE_FILE_TOKENS = 25000;
19
+ const MAX_TOTAL_FILE_TOKENS = 150000;
20
+ const MAX_TOTAL_FILES = 30;
21
+ const MAX_TOOL_OUTPUT_TOKENS = 10000;
22
+ /**
23
+ * Bounds individual file content to ensure it does not exceed the single-file token budget.
24
+ *
25
+ * @param filePath The file path.
26
+ * @param content Raw file text.
27
+ * @returns Budget-constrained file text.
28
+ */
29
+ function boundFileContent(filePath, content) {
30
+ const tokens = estimateTokenCount(content);
31
+ if (tokens <= MAX_SINGLE_FILE_TOKENS) {
32
+ return content;
33
+ }
34
+ debugLog(`Truncating large file context for ${filePath}: ${tokens} tokens -> capped at ${MAX_SINGLE_FILE_TOKENS}`);
35
+ return pruneTextToTokenBudget(content, MAX_SINGLE_FILE_TOKENS, {
36
+ truncationMarker: `\n\n... [File content truncated by Context Agent: ${tokens} estimated tokens exceeded the ${MAX_SINGLE_FILE_TOKENS} token limit. Use read_file with line ranges if specific sections are needed.] ...\n\n`,
37
+ });
38
+ }
39
+ /**
40
+ * Bounds tool output text to prevent massive outputs from blowing the context window during investigation turns.
41
+ *
42
+ * @param output Raw tool output string.
43
+ * @returns Budget-constrained tool output.
44
+ */
45
+ function boundToolOutput(output) {
46
+ const tokens = estimateTokenCount(output);
47
+ if (tokens <= MAX_TOOL_OUTPUT_TOKENS) {
48
+ return output;
49
+ }
50
+ return pruneTextToTokenBudget(output, MAX_TOOL_OUTPUT_TOKENS, {
51
+ truncationMarker: `\n\n... [Tool output truncated: exceeded ${MAX_TOOL_OUTPUT_TOKENS} tokens limit] ...\n`,
52
+ });
53
+ }
13
54
  async function detectProjectType(workspaceRoot) {
14
55
  const types = [];
15
56
  const fileExists = async (fileName) => {
@@ -149,7 +190,8 @@ export async function routeIntent(userRequest, chatHistory = '', abortSignal) {
149
190
  const session = createIntentRouterSession();
150
191
  let prompt = `User Request: "${userRequest}"`;
151
192
  if (chatHistory) {
152
- prompt = `Previous Conversation Context:\n${chatHistory}\n\n${prompt}`;
193
+ const prunedHistory = pruneTextToTokenBudget(chatHistory, MAX_CHAT_HISTORY_ROUTER_TOKENS, { fromStart: true });
194
+ prompt = `Previous Conversation Context:\n${prunedHistory}\n\n${prompt}`;
153
195
  }
154
196
  const result = await session.sendMessage(prompt, undefined, abortSignal);
155
197
  const text = result.response.text()?.trim() || '{}';
@@ -183,7 +225,8 @@ export async function evaluateExecutionComplexity(userRequest, investigationSumm
183
225
  Investigation Summary: ${investigationSummary || 'None (0 files needed)'}
184
226
  Number of Relevant Files: ${numRelevantFiles}`;
185
227
  if (chatHistory) {
186
- prompt = `Previous Conversation Context:\n${chatHistory}\n\n${prompt}`;
228
+ const prunedHistory = pruneTextToTokenBudget(chatHistory, MAX_CHAT_HISTORY_ROUTER_TOKENS, { fromStart: true });
229
+ prompt = `Previous Conversation Context:\n${prunedHistory}\n\n${prompt}`;
187
230
  }
188
231
  const result = await session.sendMessage(prompt, undefined, abortSignal);
189
232
  const text = result.response.text()?.trim() || '{}';
@@ -235,8 +278,11 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
235
278
  const label = alias ? `@${alias} (${root})` : `Primary Workspace (${root})`;
236
279
  const treeResult = await executeTool(root, 'list_directory', { dirPath: '.', maxDepth: 10 });
237
280
  let tree = treeResult.output;
238
- if (tree.length > 30000) {
239
- tree = tree.substring(0, 30000) + '\n... (Project tree truncated due to size)';
281
+ const treeTokens = estimateTokenCount(tree);
282
+ if (treeTokens > MAX_TREE_TOKENS) {
283
+ tree = pruneTextToTokenBudget(tree, MAX_TREE_TOKENS, {
284
+ truncationMarker: '\n... (Project tree truncated to fit token budget)\n',
285
+ });
240
286
  }
241
287
  const type = await detectProjectType(root);
242
288
  if (!alias) {
@@ -275,20 +321,27 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
275
321
  if (onProgress)
276
322
  onProgress(`⚡ Memory Bank HIT — loaded ${cacheHit.entry.relevantFiles.length} files from cache`);
277
323
  const cachedFiles = new Map();
278
- for (const filePath of cacheHit.entry.relevantFiles) {
279
- if (abortSignal.aborted) {
280
- const err = new Error('Operation aborted');
281
- err.name = 'AbortError';
282
- throw err;
283
- }
324
+ let cumulativeFileTokens = 0;
325
+ const readResults = await Promise.all(cacheHit.entry.relevantFiles.map(async (filePath) => {
326
+ if (abortSignal.aborted)
327
+ return { filePath, error: 'aborted' };
284
328
  const readResult = await executeTool(workspaceRoot, 'read_file', { filePath });
285
- if (!readResult.error) {
286
- cachedFiles.set(filePath, { text: readResult.output, inlineData: readResult.inlineData });
329
+ return { filePath, readResult };
330
+ }));
331
+ for (const item of readResults) {
332
+ if (item.readResult && !item.readResult.error) {
333
+ const boundedText = boundFileContent(item.filePath, item.readResult.output);
334
+ const tokens = estimateTokenCount(boundedText);
335
+ if (cumulativeFileTokens + tokens <= MAX_TOTAL_FILE_TOKENS) {
336
+ cachedFiles.set(item.filePath, { text: boundedText, inlineData: item.readResult.inlineData });
337
+ cumulativeFileTokens += tokens;
338
+ }
287
339
  }
288
340
  }
289
341
  const MAX_TOTAL_FILES = 30;
290
342
  try {
291
343
  const { resolveAndValidateMultiWorkspacePath } = await import('../utils/pathSecurity.js');
344
+ const depsToRead = [];
292
345
  const autoDiscovered = new Set();
293
346
  for (const filePath of cacheHit.entry.relevantFiles) {
294
347
  if (abortSignal.aborted)
@@ -298,21 +351,34 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
298
351
  const graph = await buildDependencyGraph(resolved.workspaceRoot);
299
352
  const reverseDeps = graph.getImportedBy(resolved.relativePath);
300
353
  for (const dep of reverseDeps) {
301
- if (abortSignal.aborted)
302
- break;
303
354
  if (cachedFiles.has(dep) || autoDiscovered.has(dep))
304
355
  continue;
305
356
  if (cachedFiles.size + autoDiscovered.size >= MAX_TOTAL_FILES)
306
357
  break;
307
358
  autoDiscovered.add(dep);
308
- const depReadRes = await executeTool(resolved.workspaceRoot, 'read_file', { filePath: dep });
309
- if (!depReadRes.error) {
310
- cachedFiles.set(dep, { text: depReadRes.output, inlineData: depReadRes.inlineData });
311
- }
359
+ depsToRead.push({ dep, resolvedWorkspace: resolved.workspaceRoot });
312
360
  }
313
361
  }
314
362
  catch (e) { }
315
363
  }
364
+ if (depsToRead.length > 0) {
365
+ const depResults = await Promise.all(depsToRead.map(async ({ dep, resolvedWorkspace }) => {
366
+ if (abortSignal.aborted)
367
+ return { dep, error: 'aborted' };
368
+ const depReadRes = await executeTool(resolvedWorkspace, 'read_file', { filePath: dep });
369
+ return { dep, depReadRes };
370
+ }));
371
+ for (const item of depResults) {
372
+ if (item.depReadRes && !item.depReadRes.error) {
373
+ const boundedText = boundFileContent(item.dep, item.depReadRes.output);
374
+ const tokens = estimateTokenCount(boundedText);
375
+ if (cumulativeFileTokens + tokens <= MAX_TOTAL_FILE_TOKENS) {
376
+ cachedFiles.set(item.dep, { text: boundedText, inlineData: item.depReadRes.inlineData });
377
+ cumulativeFileTokens += tokens;
378
+ }
379
+ }
380
+ }
381
+ }
316
382
  }
317
383
  catch (e) { }
318
384
  const { accumulateUsage } = await import('./metrics.js');
@@ -352,10 +418,12 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
352
418
  let isInvestigationFinished = false;
353
419
  let chainedMessages = [];
354
420
  let webSearchSummary = '';
355
- // Initial prompt
421
+ let cumulativeFileTokens = 0;
422
+ // Initial prompt with history budget management
356
423
  let currentMessage = `User Request: "${userRequest}"\n\nProject Type: ${projectType}\n\nProject Structure:\n${projectTree}`;
357
424
  if (chatHistory) {
358
- currentMessage = `Previous Conversation Context:\n${chatHistory}\n\n` + currentMessage;
425
+ const prunedHistory = pruneTextToTokenBudget(chatHistory, MAX_CHAT_HISTORY_INVESTIGATION_TOKENS, { fromStart: true });
426
+ currentMessage = `Previous Conversation Context:\n${prunedHistory}\n\n` + currentMessage;
359
427
  }
360
428
  currentMessage += `\n\nStart investigating to find relevant files.`;
361
429
  const MAX_TURNS = Infinity;
@@ -403,11 +471,11 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
403
471
  break;
404
472
  }
405
473
  let isFinished = false;
406
- const functionResponses = [];
474
+ // Log progress for all requested tool calls in this turn
407
475
  for (const call of functionCalls) {
408
476
  if (abortSignal.aborted)
409
477
  break;
410
- const args = call.args;
478
+ const args = (call.args || {});
411
479
  let logMsg = ` [Context Agent] Executing ${call.name}`;
412
480
  if (call.name === 'finish_investigation') {
413
481
  const filesToRead = args.relevantFiles || [];
@@ -434,69 +502,84 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
434
502
  logMsg = ` [Context Agent] Looking for recently modified files`;
435
503
  }
436
504
  else if (call.name === 'run_analysis_script') {
437
- const target = args.targetFile ? ` for: ${args.targetFile}` : '';
438
- logMsg = ` [Context Agent] Running analysis script${target}`;
439
- }
440
- const cleanMsg = logMsg.trim().replace(/^\[Context Agent\] /, '');
441
- if (onToolCall) {
442
- onToolCall(cleanMsg, 'Context Agent');
505
+ logMsg = ` [Context Agent] Running analysis script (${args.language})`;
443
506
  }
444
- else if (onProgress) {
445
- onProgress(cleanMsg);
507
+ if (onProgress) {
508
+ onProgress(logMsg.trim());
446
509
  }
447
510
  else {
448
511
  console.log(pc.dim(logMsg));
449
512
  }
513
+ }
514
+ // Execute tool calls concurrently while preserving 1:1 Gemini response positional ordering
515
+ const functionResponses = await Promise.all(functionCalls.map(async (call) => {
516
+ if (abortSignal.aborted) {
517
+ return {
518
+ functionResponse: {
519
+ name: call.name,
520
+ response: { error: 'Operation aborted.' },
521
+ },
522
+ };
523
+ }
524
+ const args = (call.args || {});
450
525
  if (call.name === 'finish_investigation') {
526
+ isInvestigationFinished = true;
527
+ isFinished = true;
451
528
  summary = args.summary || '';
452
529
  const filesToRead = args.relevantFiles || [];
453
- debugLog(`Context Agent finished. Selected files: ${JSON.stringify(filesToRead)}`);
454
- for (const filePath of filesToRead) {
455
- if (!relevantFiles.has(filePath)) {
456
- const readResult = await executeTool(workspaceRoot, 'read_file', { filePath });
457
- if (!readResult.error) {
458
- relevantFiles.set(filePath, { text: readResult.output, inlineData: readResult.inlineData });
459
- }
530
+ const uniqueFilesToRead = filesToRead.filter((fp) => !relevantFiles.has(fp));
531
+ const readResults = await Promise.all(uniqueFilesToRead.map(async (filePath) => {
532
+ const readResult = await executeTool(workspaceRoot, 'read_file', { filePath });
533
+ return { filePath, readResult };
534
+ }));
535
+ for (const { filePath, readResult } of readResults) {
536
+ if (cumulativeFileTokens >= MAX_TOTAL_FILE_TOKENS)
537
+ break;
538
+ if (!readResult.error) {
539
+ const boundedText = boundFileContent(filePath, readResult.output);
540
+ relevantFiles.set(filePath, { text: boundedText, inlineData: readResult.inlineData });
541
+ cumulativeFileTokens += estimateTokenCount(boundedText);
460
542
  }
461
543
  }
462
- isFinished = true;
463
- isInvestigationFinished = true;
464
- // ── Auto-trace reverse dependencies ──
465
- // When the Context Agent finalizes its investigation, we automatically
466
- // discover files that DEPEND ON the selected files. This ensures the
467
- // Execution Agent won't break imports when modifying/deleting/renaming.
468
- const MAX_TOTAL_FILES = 30;
544
+ // Auto-trace reverse dependencies for all selected files to include active importers
469
545
  try {
546
+ const { buildDependencyGraph } = await import('../utils/dependencyTracer.js');
470
547
  const { resolveAndValidateMultiWorkspacePath } = await import('../utils/pathSecurity.js');
471
548
  const autoDiscovered = new Set();
472
- for (const filePath of filesToRead) {
549
+ await Promise.all(filesToRead.map(async (filePath) => {
473
550
  try {
474
551
  const resolved = resolveAndValidateMultiWorkspacePath(workspaceRoot, filePath);
475
552
  const graph = await buildDependencyGraph(resolved.workspaceRoot);
476
553
  const reverseDeps = graph.getImportedBy(resolved.relativePath);
477
554
  for (const dep of reverseDeps) {
478
555
  const aliasedDep = resolved.alias ? `@${resolved.alias}/${dep}` : dep;
479
- if (!filesToRead.includes(aliasedDep) && !autoDiscovered.has(aliasedDep)) {
556
+ if (!filesToRead.includes(aliasedDep)) {
480
557
  autoDiscovered.add(aliasedDep);
481
558
  }
482
559
  }
483
560
  }
484
- catch (e) {
561
+ catch {
485
562
  // Ignore path resolution errors for trace dependencies
486
563
  }
487
- }
488
- // Merge auto-discovered dependents, respecting the file cap
564
+ }));
565
+ // Merge auto-discovered dependents, respecting both file count and token budget caps
489
566
  const remaining = MAX_TOTAL_FILES - relevantFiles.size;
567
+ const depsToRead = Array.from(autoDiscovered)
568
+ .filter((dep) => !relevantFiles.has(dep))
569
+ .slice(0, Math.max(0, remaining));
570
+ const depReadResults = await Promise.all(depsToRead.map(async (dep) => {
571
+ const readResult = await executeTool(workspaceRoot, 'read_file', { filePath: dep });
572
+ return { dep, readResult };
573
+ }));
490
574
  let added = 0;
491
- for (const dep of autoDiscovered) {
492
- if (added >= remaining)
575
+ for (const { dep, readResult } of depReadResults) {
576
+ if (relevantFiles.size >= MAX_TOTAL_FILES || cumulativeFileTokens >= MAX_TOTAL_FILE_TOKENS)
493
577
  break;
494
- if (!relevantFiles.has(dep)) {
495
- const readResult = await executeTool(workspaceRoot, 'read_file', { filePath: dep });
496
- if (!readResult.error) {
497
- relevantFiles.set(dep, { text: readResult.output, inlineData: readResult.inlineData });
498
- added++;
499
- }
578
+ if (!readResult.error) {
579
+ const boundedText = boundFileContent(dep, readResult.output);
580
+ relevantFiles.set(dep, { text: boundedText, inlineData: readResult.inlineData });
581
+ cumulativeFileTokens += estimateTokenCount(boundedText);
582
+ added++;
500
583
  }
501
584
  }
502
585
  if (added > 0) {
@@ -510,46 +593,50 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
510
593
  catch {
511
594
  // Dependency tracing is best-effort — don't block investigation
512
595
  }
513
- functionResponses.push({
596
+ return {
514
597
  functionResponse: {
515
598
  name: call.name,
516
599
  response: { output: 'Investigation finished.' },
517
600
  },
518
- });
519
- break;
601
+ };
520
602
  }
521
603
  else if (call.name === 'list_directory') {
522
604
  const listRes = await executeTool(workspaceRoot, 'list_directory', args);
523
- functionResponses.push({
605
+ const output = boundToolOutput(listRes.output);
606
+ return {
524
607
  functionResponse: {
525
608
  name: call.name,
526
609
  response: {
527
- output: listRes.output,
610
+ output,
528
611
  ...(listRes.error ? { error: listRes.error } : {}),
529
612
  },
530
613
  },
531
- });
614
+ };
532
615
  }
533
616
  else if (call.name === 'search_codebase') {
534
617
  const grepRes = await executeTool(workspaceRoot, 'grep_search', { ...args, workspace: 'all' });
535
- functionResponses.push({
618
+ const output = boundToolOutput(grepRes.error ? grepRes.error : grepRes.output);
619
+ return {
536
620
  functionResponse: {
537
621
  name: call.name,
538
- response: { output: grepRes.error ? grepRes.error : grepRes.output },
622
+ response: { output },
539
623
  },
540
- });
624
+ };
541
625
  }
542
626
  else if (call.name === 'read_file') {
543
627
  const readRes = await executeTool(workspaceRoot, 'read_file', args);
544
628
  if (!readRes.error) {
545
- relevantFiles.set(args.filePath, { text: readRes.output, inlineData: readRes.inlineData });
629
+ const boundedText = boundFileContent(args.filePath, readRes.output);
630
+ relevantFiles.set(args.filePath, { text: boundedText, inlineData: readRes.inlineData });
631
+ cumulativeFileTokens += estimateTokenCount(boundedText);
546
632
  }
547
- functionResponses.push({
633
+ const output = boundToolOutput(readRes.error ? readRes.error : readRes.output);
634
+ return {
548
635
  functionResponse: {
549
636
  name: call.name,
550
- response: { output: readRes.error ? readRes.error : readRes.output },
637
+ response: { output },
551
638
  },
552
- });
639
+ };
553
640
  }
554
641
  else if (call.name === 'perform_web_search') {
555
642
  try {
@@ -564,56 +651,64 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
564
651
  console.log(pc.dim(` [Web Search] ${webMsg}`));
565
652
  }
566
653
  const searchSummary = webResult.response.text()?.trim() || 'No relevant information found.';
567
- webSearchSummary += `\nQuery: ${args.query}\nFindings:\n${searchSummary}\n`;
568
- functionResponses.push({
654
+ const boundedSummary = boundToolOutput(searchSummary);
655
+ webSearchSummary += `\nQuery: ${args.query}\nFindings:\n${boundedSummary}\n`;
656
+ return {
569
657
  functionResponse: {
570
658
  name: call.name,
571
- response: { output: searchSummary },
659
+ response: { output: boundedSummary },
572
660
  },
573
- });
661
+ };
574
662
  }
575
663
  catch (e) {
576
- functionResponses.push({
664
+ return {
577
665
  functionResponse: {
578
666
  name: call.name,
579
667
  response: { error: e.message || 'Failed to search the web' },
580
668
  },
581
- });
669
+ };
582
670
  }
583
671
  }
584
672
  else if (call.name === 'find_dependencies') {
585
673
  const depResult = await executeTool(workspaceRoot, 'find_dependencies', args);
586
- functionResponses.push({
674
+ const output = boundToolOutput(depResult.error ? depResult.error : depResult.output);
675
+ return {
587
676
  functionResponse: {
588
677
  name: call.name,
589
- response: { output: depResult.error ? depResult.error : depResult.output },
678
+ response: { output },
590
679
  },
591
- });
680
+ };
592
681
  }
593
682
  else if (call.name === 'find_recent_changes') {
594
683
  const recentRes = await executeTool(workspaceRoot, 'find_recent_changes', args);
595
- functionResponses.push({
684
+ const output = boundToolOutput(recentRes.error ? recentRes.error : recentRes.output);
685
+ return {
596
686
  functionResponse: {
597
687
  name: call.name,
598
- response: { output: recentRes.error ? recentRes.error : recentRes.output },
688
+ response: { output },
599
689
  },
600
- });
690
+ };
601
691
  }
602
692
  else if (call.name === 'run_analysis_script') {
603
- const analysisResult = await runEphemeralScript(workspaceRoot, args.language, args.code, {
604
- abortSignal,
605
- });
606
- const output = analysisResult.exitCode === 0
693
+ const analysisResult = await runEphemeralScript(workspaceRoot, args.language, args.code, { abortSignal });
694
+ const rawOutput = analysisResult.exitCode === 0
607
695
  ? analysisResult.stdout || '(script produced no output)'
608
696
  : `Script failed (exit ${analysisResult.exitCode}):\n${analysisResult.stderr}`;
609
- functionResponses.push({
697
+ const output = boundToolOutput(rawOutput);
698
+ return {
610
699
  functionResponse: {
611
700
  name: call.name,
612
701
  response: { output },
613
702
  },
614
- });
703
+ };
615
704
  }
616
- }
705
+ return {
706
+ functionResponse: {
707
+ name: call.name,
708
+ response: { output: `Unknown tool: ${call.name}` },
709
+ },
710
+ };
711
+ }));
617
712
  if (isFinished) {
618
713
  break;
619
714
  }