From 8c303ea35dd74096c85eaab8507eb123e884155f Mon Sep 17 00:00:00 2001 From: Phototonic <35551999+Phototonic@users.noreply.github.com> Date: Fri, 23 Oct 2026 21:35:42 +1100 Subject: [PATCH] fix(auto-capture): read tool names from v2-shaped tool parts On OpenCode v2 hosts, session tool parts carry the tool name at part.name (v1 wire format used part.tool). extractAIContent only read tool.tool, so the auto-capture prompt's Tools Used section always listed unknown(). Read name first with the v1 field kept as a fallback, and cover both part shapes with extraction regression tests. --- src/services/auto-capture.ts | 4 +-- tests/auto-capture-context.test.ts | 52 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/services/auto-capture.ts b/src/services/auto-capture.ts index db756431..62894621 100644 --- a/src/services/auto-capture.ts +++ b/src/services/auto-capture.ts @@ -271,7 +271,7 @@ function getAIResponseMessages(messages: any[], promptMessageId: string): any[] return responseMessages; } -function extractAIContent(messages: any[]): { +export function extractAIContent(messages: any[]): { textResponses: string[]; toolCalls: ToolCallInfo[]; } { @@ -293,7 +293,7 @@ function extractAIContent(messages: any[]): { const toolParts = msg.parts.filter((p: any) => p.type === "tool"); for (const tool of toolParts) { - const name = tool.tool || "unknown"; + const name = tool.name || tool.tool || "unknown"; let input = ""; if (tool.state?.input) { diff --git a/tests/auto-capture-context.test.ts b/tests/auto-capture-context.test.ts index 681df4b4..aea35914 100644 --- a/tests/auto-capture-context.test.ts +++ b/tests/auto-capture-context.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "bun:test"; import { buildBoundedSummaryPrompt, buildMarkdownContext, + extractAIContent, getAutoCaptureMarkdownBudget, } from "../src/services/auto-capture.js"; import { utf8ByteLength } from "../src/utils/context-limit.js"; @@ -96,3 +97,54 @@ describe("buildMarkdownContext budgeting (#232)", () => { expect(userPrompt).toContain("truncated to autoCaptureMaxContextBytes"); }); }); + +describe("extractAIContent tool calls", () => { + it("extracts tool names from v2-shaped parts", () => { + const { toolCalls } = extractAIContent([ + { + info: { role: "assistant" }, + parts: [ + { + type: "tool", + id: "t1", + name: "bash", + state: { status: "completed", input: { command: "ls" } }, + }, + ], + }, + ]); + + expect(toolCalls).toHaveLength(1); + expect(toolCalls[0]?.name).toBe("bash"); + expect(toolCalls[0]?.input).toContain('command: "ls"'); + }); + + it("keeps extracting tool names from v1-shaped parts", () => { + const { toolCalls } = extractAIContent([ + { + info: { role: "assistant" }, + parts: [ + { + type: "tool", + callID: "call_1", + tool: "read", + state: { input: { path: "/tmp/x" } }, + }, + ], + }, + ]); + + expect(toolCalls[0]?.name).toBe("read"); + }); + + it("falls back to unknown when a tool part has no name", () => { + const { toolCalls } = extractAIContent([ + { + info: { role: "assistant" }, + parts: [{ type: "tool", state: { input: {} } }], + }, + ]); + + expect(toolCalls[0]?.name).toBe("unknown"); + }); +});