From f7821616f7065510c590ca7ec4fc432a500eeae8 Mon Sep 17 00:00:00 2001 From: ilya-bov <111734093+ilya-bov@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:49:52 +0300 Subject: [PATCH] fix: surface response tool output and stop loop after final response --- .../last30days/scripts/last30days.py | 1 + src/components/chat/message-bubble.tsx | 35 +++++++++++++++++-- src/lib/agent/agent.ts | 7 ++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/bundled-skills/last30days/scripts/last30days.py b/bundled-skills/last30days/scripts/last30days.py index c6b119f..b62e515 100644 --- a/bundled-skills/last30days/scripts/last30days.py +++ b/bundled-skills/last30days/scripts/last30days.py @@ -145,6 +145,7 @@ from lib import ( schema, score, ui, + websearch, xai_x, youtube_yt, ) diff --git a/src/components/chat/message-bubble.tsx b/src/components/chat/message-bubble.tsx index 2cc13ee..851c5e7 100644 --- a/src/components/chat/message-bubble.tsx +++ b/src/components/chat/message-bubble.tsx @@ -25,6 +25,35 @@ export function MessageBubble({ message }: MessageBubbleProps) { (p) => p.type.startsWith("tool-") || p.type === "dynamic-tool" ); + // The agent often emits final answers via the `response` tool with no text part. + // Surface that output as regular assistant text so the user always sees it. + const responseToolText = toolParts + .map((part) => { + if (part.type === "dynamic-tool") { + const dp = part as { + toolName?: string; + state?: string; + output?: unknown; + }; + if (dp.toolName !== "response" || dp.state !== "output-available") return ""; + return typeof dp.output === "string" ? dp.output : JSON.stringify(dp.output ?? ""); + } + + if (!part.type.startsWith("tool-")) return ""; + const tp = part as { + type: string; + state?: string; + output?: unknown; + }; + const toolName = tp.type.replace("tool-", ""); + if (toolName !== "response" || tp.state !== "output-available") return ""; + return typeof tp.output === "string" ? tp.output : JSON.stringify(tp.output ?? ""); + }) + .filter(Boolean) + .join("\n\n"); + + const visibleTextContent = textContent || responseToolText; + return (
{/* Tool invocations */} @@ -94,7 +123,7 @@ export function MessageBubble({ message }: MessageBubbleProps) { })} {/* Text content: same font size for user and AI, first line aligned with icon center */} - {textContent && ( + {visibleTextContent && (
{isUser ? ( -

{textContent}

+

{visibleTextContent}

) : (
- +
)}
diff --git a/src/lib/agent/agent.ts b/src/lib/agent/agent.ts index cefee4d..b12fa7d 100644 --- a/src/lib/agent/agent.ts +++ b/src/lib/agent/agent.ts @@ -2,6 +2,7 @@ import { streamText, generateText, stepCountIs, + hasToolCall, type ModelMessage, type ToolExecutionOptions, type ToolSet, @@ -690,7 +691,7 @@ export async function runAgent(options: { system: systemPrompt, messages, tools, - stopWhen: stepCountIs(MAX_TOOL_STEPS_PER_TURN), + stopWhen: [stepCountIs(MAX_TOOL_STEPS_PER_TURN), hasToolCall("response")], temperature: settings.chatModel.temperature ?? 0.7, maxOutputTokens: settings.chatModel.maxTokens ?? 4096, onFinish: async (event) => { @@ -869,7 +870,7 @@ export async function runAgentText(options: { system: systemPrompt, messages, tools, - stopWhen: stepCountIs(MAX_TOOL_STEPS_PER_TURN), + stopWhen: [stepCountIs(MAX_TOOL_STEPS_PER_TURN), hasToolCall("response")], temperature: settings.chatModel.temperature ?? 0.7, maxOutputTokens: settings.chatModel.maxTokens ?? 4096, }); @@ -1000,7 +1001,7 @@ export async function runSubordinateAgent(options: { system: systemPrompt, messages, tools, - stopWhen: stepCountIs(MAX_TOOL_STEPS_SUBORDINATE), + stopWhen: [stepCountIs(MAX_TOOL_STEPS_SUBORDINATE), hasToolCall("response")], temperature: settings.chatModel.temperature ?? 0.7, maxOutputTokens: settings.chatModel.maxTokens ?? 4096, });