From e1ab81ba728bfd41a1a3f249740d656d2eca849f Mon Sep 17 00:00:00 2001 From: hugocasa Date: Thu, 17 Sep 2026 10:12:01 +0200 Subject: [PATCH] fix: show an agent run's thinking instead of falling back to raw json Co-Authored-By: Claude Opus 5 (1M context) --- .../lib/components/AgentResultDisplay.svelte | 18 ++++++++++++++++++ .../src/lib/components/aiAgentResult.test.ts | 5 +++++ frontend/src/lib/components/aiAgentResult.ts | 13 ++++++++++--- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/components/AgentResultDisplay.svelte b/frontend/src/lib/components/AgentResultDisplay.svelte index ec10bbaf03..1397643e4b 100644 --- a/frontend/src/lib/components/AgentResultDisplay.svelte +++ b/frontend/src/lib/components/AgentResultDisplay.svelte @@ -3,6 +3,7 @@ import { Badge } from '$lib/components/common' import GfmMarkdown from './GfmMarkdown.svelte' import AgentTrace from './AgentTrace.svelte' + import ChatCollapsibleCard from './copilot/chat/ChatCollapsibleCard.svelte' import LabeledDivider from './LabeledDivider.svelte' import WebSearchSourcesDisplay from './copilot/chat/WebSearchSourcesDisplay.svelte' import { buildAgentTrace, splitFinalAnswer } from './agentTrace' @@ -35,6 +36,8 @@ // answered itself. Its citations move down with it. let answer = $derived(splitFinalAnswer(buildAgentTrace(result.messages), result.output)) let trace = $derived(answer.trace) + let reasoning = $derived(result.reasoning?.trim()) + let reasoningExpanded = $state(false) let anchor: HTMLElement | undefined = $state() const sticker = createBottomSticker() @@ -43,6 +46,7 @@ // so the end has moved from wherever the stream had the reader parked. runKey trace.length + reasoning sticker.scrollToEnd(runPane(anchor)) }) @@ -50,6 +54,20 @@
{#if trace.length > 0} + {/if} + {#if reasoning} + + (reasoningExpanded = !reasoningExpanded)} + contentClass="font-main" + > + + + {/if} + {#if trace.length > 0 || reasoning} Output diff --git a/frontend/src/lib/components/aiAgentResult.test.ts b/frontend/src/lib/components/aiAgentResult.test.ts index 75a7ccc896..4c6499ce73 100644 --- a/frontend/src/lib/components/aiAgentResult.test.ts +++ b/frontend/src/lib/components/aiAgentResult.test.ts @@ -22,6 +22,11 @@ describe('parseAgentResult', () => { it('accepts the envelope with and without its optional keys', () => { expect(parseAgentResult(envelope)?.output).toBe('the answer') expect(parseAgentResult({ output: 1, messages: [{ role: 'user' }] })?.messages).toHaveLength(1) + // A key the worker serializes and the signature does not know costs every run + // carrying it the pretty display, so each one is pinned here. + expect(parseAgentResult({ ...envelope, reasoning: 'let me think' })?.reasoning).toBe( + 'let me think' + ) }) // The signature is the only thing separating an agent result from any other diff --git a/frontend/src/lib/components/aiAgentResult.ts b/frontend/src/lib/components/aiAgentResult.ts index e8328a1a43..5b314e552c 100644 --- a/frontend/src/lib/components/aiAgentResult.ts +++ b/frontend/src/lib/components/aiAgentResult.ts @@ -31,10 +31,16 @@ export type AgentResult = { messages: AgentMessage[] usage?: AgentTokenUsage wm_stream?: string + /** The model's thinking across every iteration, blank-line separated. */ + reasoning?: string } -/** Every key `AIAgentResult` can serialize. `usage` and `wm_stream` are skipped when empty. */ -const ENVELOPE_KEYS = ['output', 'messages', 'usage', 'wm_stream'] +/** + * Every key `AIAgentResult` can serialize; the optional ones are skipped when + * empty. The signature below is closed, so a key the worker gains and this list + * does not makes every run carrying it fall back to the raw JSON. + */ +const ENVELOPE_KEYS = ['output', 'messages', 'usage', 'wm_stream', 'reasoning'] function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value) @@ -129,7 +135,8 @@ export function parseAgentResult(result: unknown): AgentResult | undefined { output: result.output, messages: toAgentMessages(result.messages), usage: isRecord(result.usage) ? (result.usage as AgentTokenUsage) : undefined, - wm_stream: typeof result.wm_stream === 'string' ? result.wm_stream : undefined + wm_stream: typeof result.wm_stream === 'string' ? result.wm_stream : undefined, + reasoning: typeof result.reasoning === 'string' ? result.reasoning : undefined } }