From 6d574549bcd6f0b210ba4e7a0c08d3f03f30795c Mon Sep 17 00:00:00 2001 From: Daniel Rodriguez Date: Wed, 21 Jan 2026 18:46:41 -0600 Subject: [PATCH 1/7] fix: include _noop tool in activeTools for LiteLLM proxy compatibility (#9912) --- packages/opencode/src/session/llm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index e73f20403f3..55c9c452473 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -208,7 +208,7 @@ export namespace LLM { topP: params.topP, topK: params.topK, providerOptions: ProviderTransform.providerOptions(input.model, params.options), - activeTools: Object.keys(tools).filter((x) => x !== "invalid" && x !== "_noop"), + activeTools: Object.keys(tools).filter((x) => x !== "invalid"), tools, maxOutputTokens, abortSignal: input.abort, From 65e267ed3a83168848da5c040b56e28400430c6e Mon Sep 17 00:00:00 2001 From: dpuyosa Date: Thu, 22 Jan 2026 03:28:04 +0100 Subject: [PATCH 2/7] feat: Add promptCacheKey for Venice provider (#9915) --- packages/opencode/src/provider/transform.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 6148b66b5c9..8b6bba903aa 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -598,6 +598,11 @@ export namespace ProviderTransform { result["reasoningSummary"] = "auto" } } + + if (input.model.providerID === "venice") { + result["promptCacheKey"] = input.sessionID + } + return result } From af1e2887bddc0a0379bb6e580dc4a7dce84f022d Mon Sep 17 00:00:00 2001 From: Ronan Kearns <90280289+kearns-cu@users.noreply.github.com> Date: Wed, 21 Jan 2026 22:09:08 -0500 Subject: [PATCH 3/7] fix(app): open terminal pane when creating new terminal (#9926) --- packages/app/src/pages/session.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 560c133308f..953634ca84f 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -509,7 +509,10 @@ export default function Page() { description: language.t("command.terminal.new.description"), category: language.t("command.category.terminal"), keybind: "ctrl+alt+t", - onSelect: () => terminal.new(), + onSelect: () => { + if (terminal.all().length > 0) terminal.new() + view().terminal.open() + }, }, { id: "steps.toggle", From c3415b79fe6d1fca15edcd6eb40466c2c3802da4 Mon Sep 17 00:00:00 2001 From: luo jiyin Date: Thu, 22 Jan 2026 12:10:40 +0800 Subject: [PATCH 4/7] fix: correct spelling 'supercedes' to 'supersedes' (#9935) Signed-off-by: luojiyin --- packages/opencode/src/session/prompt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 587f9498008..185c97a75ca 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1263,7 +1263,7 @@ export namespace SessionPrompt { sessionID: userMessage.info.sessionID, type: "text", text: ` -Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received. +Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supersedes any other instructions you have received. ## Plan File Info: ${exists ? `A plan file already exists at ${plan}. You can read it and make incremental edits using the edit tool.` : `No plan file exists yet. You should create your plan at ${plan} using the write tool.`} From f1df6f2d18f1c19a67b1232f7e4c01fa91a9cb62 Mon Sep 17 00:00:00 2001 From: Caleb Norton Date: Wed, 21 Jan 2026 22:10:55 -0600 Subject: [PATCH 5/7] chore: update flake.lock (#9938) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 5ef276f0a08..16fb71c0a5a 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1768302833, - "narHash": "sha256-h5bRFy9bco+8QcK7rGoOiqMxMbmn21moTACofNLRMP4=", + "lastModified": 1768393167, + "narHash": "sha256-n2063BRjHde6DqAz2zavhOOiLUwA3qXt7jQYHyETjX8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "61db79b0c6b838d9894923920b612048e1201926", + "rev": "2f594d5af95d4fdac67fba60376ec11e482041cb", "type": "github" }, "original": { From fc0210c2fdd3194754dbe1eeff094e3038ffecbc Mon Sep 17 00:00:00 2001 From: Alex Sadleir Date: Thu, 22 Jan 2026 15:11:09 +1100 Subject: [PATCH 6/7] fix(acp): rename setSessionModel to unstable_setSessionModel (#9940) --- packages/opencode/src/acp/agent.ts | 2 +- .../opencode/test/acp/agent-interface.test.ts | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/test/acp/agent-interface.test.ts diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index e165509b9dc..d4d556485da 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -1084,7 +1084,7 @@ export namespace ACP { } } - async setSessionModel(params: SetSessionModelRequest) { + async unstable_setSessionModel(params: SetSessionModelRequest) { const session = this.sessionManager.get(params.sessionId) const model = Provider.parseModel(params.modelId) diff --git a/packages/opencode/test/acp/agent-interface.test.ts b/packages/opencode/test/acp/agent-interface.test.ts new file mode 100644 index 00000000000..a915d30ebe6 --- /dev/null +++ b/packages/opencode/test/acp/agent-interface.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, test } from "bun:test" +import { ACP } from "../../src/acp/agent" +import type { Agent as ACPAgent } from "@agentclientprotocol/sdk" + +/** + * Type-level test: This line will fail to compile if ACP.Agent + * doesn't properly implement the ACPAgent interface. + * + * The SDK checks for methods like `agent.unstable_setSessionModel` at runtime + * and throws "Method not found" if they're missing. TypeScript allows optional + * interface methods to be omitted, but the SDK still expects them. + * + * @see https://github.com/agentclientprotocol/typescript-sdk/commit/7072d3f + */ +type _AssertAgentImplementsACPAgent = ACP.Agent extends ACPAgent ? true : never +const _typeCheck: _AssertAgentImplementsACPAgent = true + +/** + * Runtime verification that optional methods the SDK expects are actually implemented. + * The SDK's router checks `if (!agent.methodName)` and throws MethodNotFound if missing. + */ +describe("acp.agent interface compliance", () => { + // Extract method names from the ACPAgent interface type + type ACPAgentMethods = keyof ACPAgent + + // Methods that the SDK's router explicitly checks for at runtime + const sdkCheckedMethods: ACPAgentMethods[] = [ + // Required + "initialize", + "newSession", + "prompt", + "cancel", + // Optional but checked by SDK router + "loadSession", + "setSessionMode", + "authenticate", + // Unstable - SDK checks these with unstable_ prefix + "unstable_listSessions", + "unstable_forkSession", + "unstable_resumeSession", + "unstable_setSessionModel", + ] + + test("Agent implements all SDK-checked methods", () => { + for (const method of sdkCheckedMethods) { + expect(typeof ACP.Agent.prototype[method as keyof typeof ACP.Agent.prototype], `Missing method: ${method}`).toBe( + "function", + ) + } + }) +}) From c2844697f38807d928368fdcd1e195e84a079077 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 21 Jan 2026 23:54:39 -0600 Subject: [PATCH 7/7] fix: ensure images are properly returned as tool results --- packages/opencode/src/session/message-v2.ts | 75 ++++++++++++++----- packages/opencode/src/session/prompt.ts | 12 --- .../opencode/test/session/message-v2.test.ts | 24 +++--- 3 files changed, 64 insertions(+), 47 deletions(-) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index d0f2beb74ab..83ca72addb1 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -435,6 +435,40 @@ export namespace MessageV2 { export function toModelMessages(input: WithParts[], model: Provider.Model): ModelMessage[] { const result: UIMessage[] = [] + const toolNames = new Set() + + const toModelOutput = (output: unknown) => { + if (typeof output === "string") { + return { type: "text", value: output } + } + + if (typeof output === "object") { + const outputObject = output as { + text: string + attachments?: Array<{ mime: string; url: string }> + } + const attachments = (outputObject.attachments ?? []).filter((attachment) => { + return attachment.url.startsWith("data:") && attachment.url.includes(",") + }) + + return { + type: "content", + value: [ + { type: "text", text: outputObject.text }, + ...attachments.map((attachment) => ({ + type: "media", + mediaType: attachment.mime, + data: iife(() => { + const commaIndex = attachment.url.indexOf(",") + return commaIndex === -1 ? attachment.url : attachment.url.slice(commaIndex + 1) + }), + })), + ], + } + } + + return { type: "json", value: output as never } + } for (const msg of input) { if (msg.parts.length === 0) continue @@ -505,31 +539,24 @@ export namespace MessageV2 { type: "step-start", }) if (part.type === "tool") { + toolNames.add(part.tool) if (part.state.status === "completed") { - if (part.state.attachments?.length) { - result.push({ - id: Identifier.ascending("message"), - role: "user", - parts: [ - { - type: "text", - text: `The tool ${part.tool} returned the following attachments:`, - }, - ...part.state.attachments.map((attachment) => ({ - type: "file" as const, - url: attachment.url, - mediaType: attachment.mime, - filename: attachment.filename, - })), - ], - }) - } + const outputText = part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output + const attachments = part.state.time.compacted ? [] : (part.state.attachments ?? []) + const output = + attachments.length > 0 + ? { + text: outputText, + attachments, + } + : outputText + assistantMessage.parts.push({ type: ("tool-" + part.tool) as `tool-${string}`, state: "output-available", toolCallId: part.callID, input: part.state.input, - output: part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output, + output, ...(differentModel ? {} : { callProviderMetadata: part.metadata }), }) } @@ -568,7 +595,15 @@ export namespace MessageV2 { } } - return convertToModelMessages(result.filter((msg) => msg.parts.some((part) => part.type !== "step-start"))) + const tools = Object.fromEntries(Array.from(toolNames).map((toolName) => [toolName, { toModelOutput }])) + + return convertToModelMessages( + result.filter((msg) => msg.parts.some((part) => part.type !== "step-start")), + { + //@ts-expect-error (convertToModelMessages expects a ToolSet but only actually needs tools[name]?.toModelOutput) + tools, + }, + ) } export const stream = fn(Identifier.schema("session"), async function* (sessionID) { diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 185c97a75ca..de62788200b 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -722,12 +722,6 @@ export namespace SessionPrompt { ) return result }, - toModelOutput(result) { - return { - type: "text", - value: result.output, - } - }, }) } @@ -819,12 +813,6 @@ export namespace SessionPrompt { content: result.content, // directly return content to preserve ordering when outputting to model } } - item.toModelOutput = (result) => { - return { - type: "text", - value: result.output, - } - } tools[key] = item } diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index b8d05643380..2f632ad1cf2 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -262,7 +262,7 @@ describe("session.message-v2.toModelMessage", () => { ]) }) - test("converts assistant tool completion into tool-call + tool-result messages and emits attachment message", () => { + test("converts assistant tool completion into tool-call + tool-result messages with attachments", () => { const userID = "m-user" const assistantID = "m-assistant" @@ -304,7 +304,7 @@ describe("session.message-v2.toModelMessage", () => { type: "file", mime: "image/png", filename: "attachment.png", - url: "https://example.com/attachment.png", + url: "data:image/png;base64,Zm9v", }, ], }, @@ -319,18 +319,6 @@ describe("session.message-v2.toModelMessage", () => { role: "user", content: [{ type: "text", text: "run tool" }], }, - { - role: "user", - content: [ - { type: "text", text: "The tool bash returned the following attachments:" }, - { - type: "file", - mediaType: "image/png", - filename: "attachment.png", - data: "https://example.com/attachment.png", - }, - ], - }, { role: "assistant", content: [ @@ -352,7 +340,13 @@ describe("session.message-v2.toModelMessage", () => { type: "tool-result", toolCallId: "call-1", toolName: "bash", - output: { type: "text", value: "ok" }, + output: { + type: "content", + value: [ + { type: "text", text: "ok" }, + { type: "media", mediaType: "image/png", data: "Zm9v" }, + ], + }, providerOptions: { openai: { tool: "meta" } }, }, ],