diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index f44df42a7..e4747ae85 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -1012,6 +1012,10 @@ class ClaudeAgentSession implements AgentSession { const server = entry?.server ?? block.server ?? "tool"; const tool = entry?.name ?? block.tool_name ?? "tool"; const status = block.is_error ? "failed" : "completed"; + + // Extract output from block.content (SDK always returns content as string) + const output = this.buildToolOutput(block, entry); + this.pushToolCall( { server, @@ -1021,7 +1025,7 @@ class ClaudeAgentSession implements AgentSession { displayName: this.buildToolDisplayName(entry), kind: this.getToolKind(entry?.classification), input: entry?.input, - output: !block.is_error && entry?.files?.length ? { files: entry.files } : undefined, + output, error: block.is_error ? block : undefined, }, items @@ -1031,6 +1035,29 @@ class ClaudeAgentSession implements AgentSession { } } + private buildToolOutput( + block: ClaudeContentChunk, + entry: ToolUseCacheEntry | undefined + ): Record | undefined { + if (block.is_error) { + return undefined; + } + + const result: Record = {}; + + // SDK tool_result blocks have content as a string + if (typeof block.content === "string" && block.content.length > 0) { + result.result = { output: block.content }; + } + + // Preserve file changes tracked during tool execution + if (entry?.files?.length) { + result.files = entry.files; + } + + return Object.keys(result).length > 0 ? result : undefined; + } + private mapPartialEvent(event: SDKPartialAssistantMessage["event"]): AgentTimelineItem[] { if (event.type === "content_block_start" && (event.content_block as ClaudeContentChunk | undefined)?.type === "tool_use") { const block = event.content_block as ClaudeContentChunk;