From 91022539d9543adef7abdf012247333c4a9541cf Mon Sep 17 00:00:00 2001 From: SSU-WEI HUANG Date: Wed, 29 Jul 2026 10:01:22 +0800 Subject: [PATCH] feat(web): show explicit subagent model (#1223) --- web/src/components/ToolCard/ToolCard.test.ts | 11 +++++++++++ web/src/components/ToolCard/ToolCard.tsx | 13 +++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/web/src/components/ToolCard/ToolCard.test.ts b/web/src/components/ToolCard/ToolCard.test.ts index 74678447..3aa2e833 100644 --- a/web/src/components/ToolCard/ToolCard.test.ts +++ b/web/src/components/ToolCard/ToolCard.test.ts @@ -169,6 +169,17 @@ describe('getSubagentModel', () => { expect(getSubagentModel(children)).toBeNull() }) + it('uses an explicit invocation model when child messages do not expose one', () => { + expect(getSubagentModel([], 'gpt-5.6-terra')).toBe('gpt-5.6-terra') + }) + + it('prefers the executed child model over an explicit invocation model', () => { + expect(getSubagentModel( + [makeAgentTextBlock({ model: 'claude-haiku-4-5-20251001' })], + 'gpt-5.6-terra' + )).toBe('Haiku 4.5') + }) + it('returns the single formatted model when every carrying child agrees, across mixed block kinds', () => { const children: ChatBlock[] = [ makeToolCallChild({ model: null }), diff --git a/web/src/components/ToolCard/ToolCard.tsx b/web/src/components/ToolCard/ToolCard.tsx index c5518b05..55ae9297 100644 --- a/web/src/components/ToolCard/ToolCard.tsx +++ b/web/src/components/ToolCard/ToolCard.tsx @@ -198,16 +198,19 @@ export function formatSubagentModelLabel(model: string): string { * the same "seenModels" pattern `aggregateResponseGroups` * (web/src/lib/assistant-runtime.ts) already uses for top-level multi-turn * message metadata, reused here rather than inventing a new convention — then - * formats each for display and joins them. + * formats each for display and joins them. When a backend does not expose + * child messages, an explicit per-invocation model is still authoritative. + * The caller/session model is deliberately never used as a fallback. */ -export function getSubagentModel(children: ChatBlock[]): string | null { +export function getSubagentModel(children: ChatBlock[], explicitModel?: string | null): string | null { const seenModels: string[] = [] for (const child of children) { if ('model' in child && child.model && !seenModels.includes(child.model)) { seenModels.push(child.model) } } - return seenModels.length > 0 ? seenModels.map(formatSubagentModelLabel).join(', ') : null + if (seenModels.length > 0) return seenModels.map(formatSubagentModelLabel).join(', ') + return explicitModel ? formatSubagentModelLabel(explicitModel) : null } function getTaskSummaryChildren(block: ToolCallBlock): { visible: ToolCallBlock[]; remaining: number } | null { @@ -420,7 +423,9 @@ function ToolCardInner(props: ToolCardProps) { const toolTitle = presentation.title const subtitle = presentation.subtitle ?? props.block.tool.description const taskSummary = renderTaskSummary(props.block, props.metadata, t) - const subagentModel = isSubagentToolName(toolName) ? getSubagentModel(props.block.children) : null + const subagentModel = isSubagentToolName(toolName) + ? getSubagentModel(props.block.children, getInputStringAny(props.block.tool.input, ['model'])) + : null const isCodexAgentCard = toolName === 'CodexAgent' const useCompactTerminalCard = shouldUseCompactTerminalToolCard(toolName, props.terminalToolDisplayMode) const showInline = shouldShowInlineToolCardBody(toolName, presentation.minimal, props.terminalToolDisplayMode)