From 115cf7eb287f51216a75957c2f77b4f5e6c1692b Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Tue, 28 Apr 2026 14:17:40 -0700 Subject: [PATCH] fix(chat): always emit narrative summary, even when tool-round cap is hit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surfaced by the live Path B test: AI fired 7 tool calls (fs.read, fs.edit, kill, dev_server.start, curl, dev_server.logs, ...) in a single turn, the loop exited at MAX_TOOL_ROUNDS, and the user saw only a tray of ✓ icons — no text reply. Two changes: 1. Bump MAX_TOOL_ROUNDS 6 → 12. Path B iteration chains routinely run long; 6 was tuned for Path A's much-shorter Coolify-orchestration sequences. 2. When the loop exits because of the cap (the last assistant turn was a tool call, not a finish), force one more no-tools Gemini call with an explicit "summarize the result, do NOT call tools" prompt. That gives the user a sentence or two of context instead of a wall of green checkmarks. Wrapped in try/catch so the stream still terminates cleanly if Gemini errors. Made-with: Cursor --- app/api/chat/route.ts | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 98a807d1..2eea5e83 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -19,7 +19,12 @@ import { callGeminiChat, streamGeminiChat } from '@/lib/ai/gemini-chat'; import { VIBN_TOOL_DEFINITIONS, executeMcpTool } from '@/lib/ai/vibn-tools'; import type { ChatMessage, ToolCall } from '@/lib/ai/gemini-chat'; -const MAX_TOOL_ROUNDS = 6; +// Bumped from 6 to 12 because Path B chains (devcontainer.ensure → +// fs.read → fs.edit → kill → start → curl → logs) routinely fire 7-10 +// tool calls in one user turn. When the cap IS hit, we still emit a +// narrative summary instead of leaving the user staring at a tool tray +// (see the no-tools follow-up call below). +const MAX_TOOL_ROUNDS = 12; let chatTablesReady = false; async function ensureChatTables() { @@ -286,6 +291,32 @@ export async function POST(request: Request) { } } + // If the loop exited because we hit MAX_TOOL_ROUNDS while the + // model still wanted to call tools, the user has only seen a + // tray of ✓ icons with no narrative. Force one final no-tools + // call so we always end on a human-readable summary. + const lastTurnHadTools = + messages.length > 0 && + messages[messages.length - 1].role === 'tool'; + if (round >= MAX_TOOL_ROUNDS && lastTurnHadTools) { + try { + const summary = await callGeminiChat({ + systemPrompt: + systemPrompt + + '\n\nYou have just executed a chain of tool calls. Summarize the result for the user in 1-3 sentences. Do NOT call any more tools.', + messages, + tools: [], + temperature: 0.3, + }); + if (summary.text) { + assistantText += summary.text; + emit({ type: 'text', text: summary.text }); + } + } catch { + // Don't let a failed summary kill the stream. + } + } + // Persist final assistant message const finalMsg: ChatMessage = { role: 'assistant',