From 2522ae6e41d1d396d87c14b55c295dce56b94d78 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 14 Jan 2026 15:17:20 -0600 Subject: [PATCH 1/5] tweak: adjust logic for store=false for codex plans --- packages/opencode/src/provider/transform.ts | 47 ++- packages/opencode/src/session/llm.ts | 8 +- .../opencode/test/provider/transform.test.ts | 63 +++- session.json | 348 ++++++++++++++++++ 4 files changed, 439 insertions(+), 27 deletions(-) create mode 100644 session.json diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 28e8d97ab6..3f433e0eb3 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -3,6 +3,7 @@ import { unique } from "remeda" import type { JSONSchema } from "zod/v4/core" import type { Provider } from "./provider" import type { ModelsDev } from "./models" +import type { Auth } from "@/auth" import { iife } from "@/util/iife" type Modality = NonNullable["input"][number] @@ -453,64 +454,70 @@ export namespace ProviderTransform { return {} } - export function options( - model: Provider.Model, - sessionID: string, - providerOptions?: Record, - ): Record { + export function options(input: { + model: Provider.Model + sessionID: string + providerOptions?: Record + auth: Auth.Info | undefined + }): Record { const result: Record = {} - if (model.api.npm === "@openrouter/ai-sdk-provider") { + // all codex plans MUST use store = false + if (input.model.providerID === "openai" && input.auth?.type === "oauth") { + result["store"] = false + } + + if (input.model.api.npm === "@openrouter/ai-sdk-provider") { result["usage"] = { include: true, } - if (model.api.id.includes("gemini-3")) { + if (input.model.api.id.includes("gemini-3")) { result["reasoning"] = { effort: "high" } } } if ( - model.providerID === "baseten" || - (model.providerID === "opencode" && ["kimi-k2-thinking", "glm-4.6"].includes(model.api.id)) + input.model.providerID === "baseten" || + (input.model.providerID === "opencode" && ["kimi-k2-thinking", "glm-4.6"].includes(input.model.api.id)) ) { result["chat_template_args"] = { enable_thinking: true } } - if (["zai", "zhipuai"].includes(model.providerID) && model.api.npm === "@ai-sdk/openai-compatible") { + if (["zai", "zhipuai"].includes(input.model.providerID) && input.model.api.npm === "@ai-sdk/openai-compatible") { result["thinking"] = { type: "enabled", clear_thinking: false, } } - if (model.providerID === "openai" || providerOptions?.setCacheKey) { - result["promptCacheKey"] = sessionID + if (input.model.providerID === "openai" || input.providerOptions?.setCacheKey) { + result["promptCacheKey"] = input.sessionID } - if (model.api.npm === "@ai-sdk/google" || model.api.npm === "@ai-sdk/google-vertex") { + if (input.model.api.npm === "@ai-sdk/google" || input.model.api.npm === "@ai-sdk/google-vertex") { result["thinkingConfig"] = { includeThoughts: true, } - if (model.api.id.includes("gemini-3")) { + if (input.model.api.id.includes("gemini-3")) { result["thinkingConfig"]["thinkingLevel"] = "high" } } - if (model.api.id.includes("gpt-5") && !model.api.id.includes("gpt-5-chat")) { - if (model.providerID.includes("codex")) { + if (input.model.api.id.includes("gpt-5") && !input.model.api.id.includes("gpt-5-chat")) { + if (input.model.providerID.includes("codex")) { result["store"] = false } - if (!model.api.id.includes("codex") && !model.api.id.includes("gpt-5-pro")) { + if (!input.model.api.id.includes("codex") && !input.model.api.id.includes("gpt-5-pro")) { result["reasoningEffort"] = "medium" } - if (model.api.id.endsWith("gpt-5.") && model.providerID !== "azure") { + if (input.model.api.id.endsWith("gpt-5.") && input.model.providerID !== "azure") { result["textVerbosity"] = "low" } - if (model.providerID.startsWith("opencode")) { - result["promptCacheKey"] = sessionID + if (input.model.providerID.startsWith("opencode")) { + result["promptCacheKey"] = input.sessionID result["include"] = ["reasoning.encrypted_content"] result["reasoningSummary"] = "auto" } diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index ebc22637e1..c8852edad0 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -95,7 +95,12 @@ export namespace LLM { !input.small && input.model.variants && input.user.variant ? input.model.variants[input.user.variant] : {} const base = input.small ? ProviderTransform.smallOptions(input.model) - : ProviderTransform.options(input.model, input.sessionID, provider.options) + : ProviderTransform.options({ + model: input.model, + sessionID: input.sessionID, + providerOptions: provider.options, + auth, + }) const options: Record = pipe( base, mergeDeep(input.model.options), @@ -104,7 +109,6 @@ export namespace LLM { ) if (isCodex) { options.instructions = SystemPrompt.instructions() - options.store = false } const params = await Plugin.trigger( diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 32b1ecb244..61da69332e 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -39,22 +39,37 @@ describe("ProviderTransform.options - setCacheKey", () => { } as any test("should set promptCacheKey when providerOptions.setCacheKey is true", () => { - const result = ProviderTransform.options(mockModel, sessionID, { setCacheKey: true }) + const result = ProviderTransform.options({ + model: mockModel, + sessionID, + providerOptions: { setCacheKey: true }, + auth: undefined, + }) expect(result.promptCacheKey).toBe(sessionID) }) test("should not set promptCacheKey when providerOptions.setCacheKey is false", () => { - const result = ProviderTransform.options(mockModel, sessionID, { setCacheKey: false }) + const result = ProviderTransform.options({ + model: mockModel, + sessionID, + providerOptions: { setCacheKey: false }, + auth: undefined, + }) expect(result.promptCacheKey).toBeUndefined() }) test("should not set promptCacheKey when providerOptions is undefined", () => { - const result = ProviderTransform.options(mockModel, sessionID, undefined) + const result = ProviderTransform.options({ + model: mockModel, + sessionID, + providerOptions: undefined, + auth: undefined, + }) expect(result.promptCacheKey).toBeUndefined() }) test("should not set promptCacheKey when providerOptions does not have setCacheKey", () => { - const result = ProviderTransform.options(mockModel, sessionID, {}) + const result = ProviderTransform.options({ model: mockModel, sessionID, providerOptions: {}, auth: undefined }) expect(result.promptCacheKey).toBeUndefined() }) @@ -68,9 +83,47 @@ describe("ProviderTransform.options - setCacheKey", () => { npm: "@ai-sdk/openai", }, } - const result = ProviderTransform.options(openaiModel, sessionID, {}) + const result = ProviderTransform.options({ model: openaiModel, sessionID, providerOptions: {}, auth: undefined }) expect(result.promptCacheKey).toBe(sessionID) }) + + test("should set store=false for openai provider with oauth auth", () => { + const openaiModel = { + ...mockModel, + providerID: "openai", + api: { + id: "gpt-4", + url: "https://api.openai.com", + npm: "@ai-sdk/openai", + }, + } + const result = ProviderTransform.options({ + model: openaiModel, + sessionID, + providerOptions: {}, + auth: { type: "oauth", refresh: "r", access: "a", expires: 0 }, + }) + expect(result.store).toBe(false) + }) + + test("should not set store=false for openai provider with api auth", () => { + const openaiModel = { + ...mockModel, + providerID: "openai", + api: { + id: "gpt-4", + url: "https://api.openai.com", + npm: "@ai-sdk/openai", + }, + } + const result = ProviderTransform.options({ + model: openaiModel, + sessionID, + providerOptions: {}, + auth: { type: "api", key: "sk-xxx" }, + }) + expect(result.store).toBeUndefined() + }) }) describe("ProviderTransform.maxOutputTokens", () => { diff --git a/session.json b/session.json new file mode 100644 index 0000000000..5d1e5c2789 --- /dev/null +++ b/session.json @@ -0,0 +1,348 @@ +{ + "info": { + "id": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "slug": "glowing-garden", + "version": "0.0.0-dev-202601142035", + "projectID": "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750", + "directory": "/Users/aidencline/development/opencode", + "title": "Greeting catch-up chat", + "time": { + "created": 1768423574827, + "updated": 1768423593422 + }, + "summary": { + "additions": 0, + "deletions": 0, + "files": 0 + } + }, + "messages": [ + { + "info": { + "id": "msg_bbe424d2f00120ijb14FQCGNc3", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "role": "user", + "time": { + "created": 1768423574839 + }, + "summary": { + "title": "Simple greeting note: Helloo", + "diffs": [] + }, + "agent": "build", + "model": { + "providerID": "openai", + "modelID": "gpt-5.2-codex" + }, + "variant": "high" + }, + "parts": [ + { + "id": "prt_bbe424d2f002jyG27z5VzBMKwa", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe424d2f00120ijb14FQCGNc3", + "type": "text", + "text": "Helloo" + } + ] + }, + { + "info": { + "id": "msg_bbe424d42001DteS1qORhxp6fn", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "role": "assistant", + "time": { + "created": 1768423574850, + "completed": 1768423579198 + }, + "parentID": "msg_bbe424d2f00120ijb14FQCGNc3", + "modelID": "gpt-5.2-codex", + "providerID": "openai", + "mode": "build", + "agent": "build", + "path": { + "cwd": "/Users/aidencline/development/opencode", + "root": "/Users/aidencline/development/opencode" + }, + "cost": 0.026537, + "tokens": { + "input": 15052, + "output": 14, + "reasoning": 0, + "cache": { + "read": 0, + "write": 0 + } + }, + "finish": "stop" + }, + "parts": [ + { + "id": "prt_bbe425dfe001tylFeRhFVxRiIV", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe424d42001DteS1qORhxp6fn", + "type": "step-start", + "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f" + }, + { + "id": "prt_bbe425dff001fd7GPeD22KjSWZ", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe424d42001DteS1qORhxp6fn", + "type": "reasoning", + "text": "", + "metadata": { + "openai": { + "itemId": "rs_0f251e66abfc3a1e006968009a62b0819c818ef7a34b35e5f1", + "reasoningEncryptedContent": "gAAAAABpaACafTEVzjtu8JssF7-OS2XXWeFodFFRHIGNQslmX51SchUx8p31PFmEGg_NNQ0O1r-WX10n_P9PZnl_EBgwLZDJUChT784MLS_pp3PyYc3HC6-eNRoDsEnzrkiUTWBiNx-U4Yq6yDriXTCMBkPjPUpSljgwQh3bT31a7pZ8PWfO2KG56zBKFljWb0UnLJjDiDL0MjNnVtb5JqZWQ8hWXUuv8o4LlYy-WqbN_Phj-gscnOd0ODbnrO6O9SDzre78Y7TwqCtqLi0bhjINViqkX72zK66-Rz8mGxmfNTCKFrVd5WUUUeM9w3SuYNRqJCyACZlMG2BPvhHpQDA2ouuCrFMHeP643hv9rSL7cz1krV0OANZTP1T_T8Pby9I9_8jZRXGIevFC6dEg_BJPDWjW3mxCN837tTuyaNb5JKCA8WPKktdp5L45UP5O5Sf3BmVUiKlRajzcnpmWCMoSzhR92YS-hZ9V6MvbclVZTol0XxXCFMw5XTx8PfWGDohnhp0UMGEqV3Dvo-__PTZITdf18tDj9ZvhMcvsYjvmoYFyw_8zXQhdT7EPqSPqdZqgqPVHR8BQNtZWTSPg3LhpCHYVPe9omf-63P4P34SWxuTOozLni-fFA2WawOCpDzbrtCLvaTrWKqKoOnxaLv3SdgV0ByggVNn-lSBnltKTzQKn3Cj_QfyV4o4UPlHi8JjbpMCpNITybbNuzIcP6jP1vWfDHqKC7Jk86AQGss2JDPuRfYdyYXfEkTPSCdgXEskgxAAvDufPPdc4AI7hTlnzhanqbUseKoKvxZrpU_KU-fB8HpM9T-_4ErMbvOgMkp7RPiS3GybBxCFgBBbhBCZhm_pCFxoTgjXEGa8iRTgZbeh5dBNVHk4=" + } + }, + "time": { + "start": 1768423579135, + "end": 1768423579135 + } + }, + { + "id": "prt_bbe425e00001V1yaBsuCYdy2kt", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe424d42001DteS1qORhxp6fn", + "type": "text", + "text": "Hey! How can I help today?", + "time": { + "start": 1768423579137, + "end": 1768423579137 + }, + "metadata": { + "openai": { + "itemId": "msg_0f251e66abfc3a1e006968009ae7f0819cb7e35354587c5ed9" + } + } + }, + { + "id": "prt_bbe425e02001wamNXw8crQaxwz", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe424d42001DteS1qORhxp6fn", + "type": "step-finish", + "reason": "stop", + "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f", + "cost": 0.026537, + "tokens": { + "input": 15052, + "output": 14, + "reasoning": 0, + "cache": { + "read": 0, + "write": 0 + } + } + } + ] + }, + { + "info": { + "id": "msg_bbe4272b3001VTE4T9MRZ9cVgd", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "role": "user", + "time": { + "created": 1768423584440 + }, + "summary": { + "title": "README explanation and project overview", + "diffs": [] + }, + "agent": "build", + "model": { + "providerID": "openai", + "modelID": "gpt-5.2-codex" + }, + "variant": "high" + }, + "parts": [ + { + "id": "prt_bbe4272b4001sm75kVdmwwc7N0", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe4272b3001VTE4T9MRZ9cVgd", + "type": "text", + "text": "read my readme and explain project" + } + ] + }, + { + "info": { + "id": "msg_bbe4272bd001zTvdwgAG7xiaI9", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "role": "assistant", + "time": { + "created": 1768423584445, + "completed": 1768423589447 + }, + "parentID": "msg_bbe4272b3001VTE4T9MRZ9cVgd", + "modelID": "gpt-5.2-codex", + "providerID": "openai", + "mode": "build", + "agent": "build", + "path": { + "cwd": "/Users/aidencline/development/opencode", + "root": "/Users/aidencline/development/opencode" + }, + "cost": 0.00323155, + "tokens": { + "input": 101, + "output": 31, + "reasoning": 0, + "cache": { + "read": 14976, + "write": 0 + } + }, + "finish": "tool-calls" + }, + "parts": [ + { + "id": "prt_bbe42860700166hdZ87CN0RnGX", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe4272bd001zTvdwgAG7xiaI9", + "type": "step-start", + "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f" + }, + { + "id": "prt_bbe4286080012Nzn3hPheDenwl", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe4272bd001zTvdwgAG7xiaI9", + "type": "reasoning", + "text": "**Planning README read**\n\nI need to read the README file and am considering using a direct path or glob to locate it, favoring reading /Users/aidencline/development/opencode/README.md.", + "metadata": { + "openai": { + "itemId": "rs_0f251e66abfc3a1e00696800a2ffa8819c8e428e32a0924bbe", + "reasoningEncryptedContent": "gAAAAABpaAClTxa2kLNStT-_Bb8iu3y5mNhaeLnCLtPi4WmNqaPUWUl36OgxVfHZ-IMi1S8TAVqeLDpuMTehuTjfmqeyii1YFR8SxlhOhKeSLZH9_eBH7XMlHFndIp-i8oM0w_QkKPPal-yQqbHd4xcgS6goX2rr7XOOpOTcX_UN2RpvQxpJa05RkbHkX2kp2_IlM0dSma7Kd9IdPsM4mYMd2RqHsn6VnUj41oL7ptcSfaIIhJS0y7JBlCi8AMxF5Z0uBeVhp_HLN4ZqfziPY_XtOrPncyBd3IwWGg-mMpq26qj03PyncgJ3WPtyIr9O-r0NHWJKtis6dcOWOem-RpJ8f9ZvMqoRlA7C2pRMP0L58sc52P9QFCyiTKs37zZ5VByFzKcgBtSD_YHvu5xWDEn9D7QEy68tjcaseL41312l_wlLpmuPjLvMyPkxAr_LP4-EJW04WHWTkObfggZu45snu6jQZm56WWa9ijLeD4d4CAFG-JNfWb-7hrIt8ZYIiS5m3TE9bMy78el2cFkior5AZplMQoo2gXhx9Q8XN9w6xFLoGLWwCtfX-Nm8tmeMDGdDStk46j3zABgS_W2dxD-auJ6rD2CDJQyzoPm-emA-KABpSm3I0XhDe4zKpRzbwPjvL9mtYbkXG4cG3tiK5G2CZEXmyta-xeC8psWUSD-fbvEyc5mbfh6rkv9SFgKHeXVSsu04V8NbKe0Nz0XOxtS5m3sO1YPqAjnsHbVeUUQxg1N47J2FAN-qkJwoLlIeoy1MwDOlq9tYLAJQkC577BHa-apNH71MexgjDhFcanv3zq5UBdTxs-x6pDufa9HDf5qmWL2tzevy9rJleVnnMZ97aylY3Q2bsnGPQBuJM_HILVed3EUf2BI08yteVbTFgIA6hRSdilsNYt8cUHJfSR-zADyrKY4OLOJXIAQcApVSRXq0GzdjXKfKbLOpomdU7RoZK8zs19TqieLPfPfMmKjQ4W3Q6of0IEfargc6lngeQtSR05ijdtoePqwHej4jSuarEGJo4SlY84R8kQLx2ZMIUvWIUmUfcOjTVo7RhEXO5kW3KAa2ZZY=" + } + }, + "time": { + "start": 1768423589384, + "end": 1768423589390 + } + }, + { + "id": "prt_bbe42860e0017PEtVVfp8s4HxM", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe4272bd001zTvdwgAG7xiaI9", + "type": "tool", + "callID": "call_52Ssi7DCzmyClgs0Zt9Z1uMT", + "tool": "read", + "state": { + "status": "completed", + "input": { + "filePath": "/Users/aidencline/development/opencode/README.md" + }, + "output": "\n00001|

\n00002| \n00003| \n00004| \n00005| \n00006| \"OpenCode\n00007| \n00008| \n00009|

\n00010|

The open source AI coding agent.

\n00011|

\n00012| \"Discord\"\n00013| \"npm\"\n00014| \"Build\n00015|

\n00016| \n00017| [![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)\n00018| \n00019| ---\n00020| \n00021| ### Installation\n00022| \n00023| ```bash\n00024| # YOLO\n00025| curl -fsSL https://opencode.ai/install | bash\n00026| \n00027| # Package managers\n00028| npm i -g opencode-ai@latest # or bun/pnpm/yarn\n00029| scoop bucket add extras; scoop install extras/opencode # Windows\n00030| choco install opencode # Windows\n00031| brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)\n00032| brew install opencode # macOS and Linux (official brew formula, updated less)\n00033| paru -S opencode-bin # Arch Linux\n00034| mise use -g opencode # Any OS\n00035| nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch\n00036| ```\n00037| \n00038| > [!TIP]\n00039| > Remove versions older than 0.1.x before installing.\n00040| \n00041| ### Desktop App (BETA)\n00042| \n00043| OpenCode is also available as a desktop application. Download directly from the [releases page](https://github.com/anomalyco/opencode/releases) or [opencode.ai/download](https://opencode.ai/download).\n00044| \n00045| | Platform | Download |\n00046| | --------------------- | ------------------------------------- |\n00047| | macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |\n00048| | macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |\n00049| | Windows | `opencode-desktop-windows-x64.exe` |\n00050| | Linux | `.deb`, `.rpm`, or AppImage |\n00051| \n00052| ```bash\n00053| # macOS (Homebrew)\n00054| brew install --cask opencode-desktop\n00055| ```\n00056| \n00057| #### Installation Directory\n00058| \n00059| The install script respects the following priority order for the installation path:\n00060| \n00061| 1. `$OPENCODE_INSTALL_DIR` - Custom installation directory\n00062| 2. `$XDG_BIN_DIR` - XDG Base Directory Specification compliant path\n00063| 3. `$HOME/bin` - Standard user binary directory (if exists or can be created)\n00064| 4. `$HOME/.opencode/bin` - Default fallback\n00065| \n00066| ```bash\n00067| # Examples\n00068| OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash\n00069| XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash\n00070| ```\n00071| \n00072| ### Agents\n00073| \n00074| OpenCode includes two built-in agents you can switch between with the `Tab` key.\n00075| \n00076| - **build** - Default, full access agent for development work\n00077| - **plan** - Read-only agent for analysis and code exploration\n00078| - Denies file edits by default\n00079| - Asks permission before running bash commands\n00080| - Ideal for exploring unfamiliar codebases or planning changes\n00081| \n00082| Also, included is a **general** subagent for complex searches and multistep tasks.\n00083| This is used internally and can be invoked using `@general` in messages.\n00084| \n00085| Learn more about [agents](https://opencode.ai/docs/agents).\n00086| \n00087| ### Documentation\n00088| \n00089| For more info on how to configure OpenCode [**head over to our docs**](https://opencode.ai/docs).\n00090| \n00091| ### Contributing\n00092| \n00093| If you're interested in contributing to OpenCode, please read our [contributing docs](./CONTRIBUTING.md) before submitting a pull request.\n00094| \n00095| ### Building on OpenCode\n00096| \n00097| If you are working on a project that's related to OpenCode and is using \"opencode\" as a part of its name; for example, \"opencode-dashboard\" or \"opencode-mobile\", please add a note to your README to clarify that it is not built by the OpenCode team and is not affiliated with us in any way.\n00098| \n00099| ### FAQ\n00100| \n00101| #### How is this different from Claude Code?\n00102| \n00103| It's very similar to Claude Code in terms of capability. Here are the key differences:\n00104| \n00105| - 100% open source\n00106| - Not coupled to any provider. Although we recommend the models we provide through [OpenCode Zen](https://opencode.ai/zen); OpenCode can be used with Claude, OpenAI, Google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider-agnostic is important.\n00107| - Out of the box LSP support\n00108| - A focus on TUI. OpenCode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.\n00109| - A client/server architecture. This for example can allow OpenCode to run on your computer, while you can drive it remotely from a mobile app. Meaning that the TUI frontend is just one of the possible clients.\n00110| \n00111| ---\n00112| \n00113| **Join our community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)\n00114| \n\n(End of file - total 114 lines)\n
", + "title": "README.md", + "metadata": { + "preview": "

\n \n \n \n \n \"OpenCode\n \n \n

\n

The open source AI coding agent.

\n

\n \"Discord\"\n \"npm\"\n \"Build\n

\n\n[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)\n\n---\n", + "truncated": false + }, + "time": { + "start": 1768423589391, + "end": 1768423589392 + } + }, + "metadata": { + "openai": { + "itemId": "fc_0f251e66abfc3a1e00696800a514d0819ca3bdcb6deb51d375" + } + } + }, + { + "id": "prt_bbe428611001gpVltZwFRQFvVV", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe4272bd001zTvdwgAG7xiaI9", + "type": "step-finish", + "reason": "tool-calls", + "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f", + "cost": 0.00323155, + "tokens": { + "input": 101, + "output": 31, + "reasoning": 0, + "cache": { + "read": 14976, + "write": 0 + } + } + } + ] + }, + { + "info": { + "id": "msg_bbe42864a001SoQKUk5dtIu7wb", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "role": "assistant", + "time": { + "created": 1768423589450, + "completed": 1768423593413 + }, + "parentID": "msg_bbe4272b3001VTE4T9MRZ9cVgd", + "modelID": "gpt-5.2-codex", + "providerID": "openai", + "mode": "build", + "agent": "build", + "path": { + "cwd": "/Users/aidencline/development/opencode", + "root": "/Users/aidencline/development/opencode" + }, + "cost": 0.0076643, + "tokens": { + "input": 1866, + "output": 127, + "reasoning": 0, + "cache": { + "read": 14976, + "write": 0 + } + }, + "finish": "stop" + }, + "parts": [ + { + "id": "prt_bbe42957c001G2nL0DDJY7Q80W", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe42864a001SoQKUk5dtIu7wb", + "type": "step-start", + "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f" + }, + { + "id": "prt_bbe42957d001neCElzalxzFjRv", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe42864a001SoQKUk5dtIu7wb", + "type": "text", + "text": "Here’s a quick overview of the project based on `README.md`:\n\n**Summary**\n- OpenCode is an open‑source AI coding agent focused on a terminal UI experience.\n- It ships as a CLI and a desktop app, with multiple install options across platforms.\n- It supports multiple model providers and emphasizes provider‑agnostic workflows.\n- It includes built‑in agents (build/plan) and a general subagent for complex tasks.\n- It uses a client/server architecture for flexible frontends beyond the TUI.\n\nWant a deeper walkthrough of specific parts (CLI, agents, SDKs, desktop app)?", + "time": { + "start": 1768423593356, + "end": 1768423593356 + }, + "metadata": { + "openai": { + "itemId": "msg_0f251e66abfc3a1e00696800a6e2f4819c9bff0c98e6a5ce3b" + } + } + }, + { + "id": "prt_bbe42958d001GOD7AK1WFYTGzO", + "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", + "messageID": "msg_bbe42864a001SoQKUk5dtIu7wb", + "type": "step-finish", + "reason": "stop", + "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f", + "cost": 0.0076643, + "tokens": { + "input": 1866, + "output": 127, + "reasoning": 0, + "cache": { + "read": 14976, + "write": 0 + } + } + } + ] + } + ] +} From f64177e5ec0eab56fe94aeb9ce2a60a97e5252f4 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 14 Jan 2026 15:30:43 -0600 Subject: [PATCH 2/5] fix: openai id issue --- packages/opencode/src/provider/transform.ts | 30 +- packages/opencode/src/session/llm.ts | 2 +- .../opencode/test/provider/transform.test.ts | 337 ++++++++++++++---- 3 files changed, 292 insertions(+), 77 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 3f433e0eb3..e49aaf875b 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -17,7 +17,31 @@ function mimeToModality(mime: string): Modality | undefined { } export namespace ProviderTransform { - function normalizeMessages(msgs: ModelMessage[], model: Provider.Model): ModelMessage[] { + function normalizeMessages( + msgs: ModelMessage[], + model: Provider.Model, + options: Record, + ): ModelMessage[] { + // Strip openai itemId metadata when store=false to avoid sending stale IDs + if (model.api.npm === "@ai-sdk/openai" && options.store === false) { + msgs = msgs.map((msg) => { + if (!Array.isArray(msg.content)) return msg + const content = msg.content.map((part) => { + if (!part.providerOptions?.openai) return part + const { itemId, reasoningEncryptedContent, ...rest } = part.providerOptions.openai as Record + const openai = Object.keys(rest).length > 0 ? rest : undefined + return { + ...part, + providerOptions: { + ...part.providerOptions, + openai, + }, + } + }) + return { ...msg, content } as typeof msg + }) + } + // Anthropic rejects messages with empty content - filter out empty string messages // and remove empty text/reasoning parts from array content if (model.api.npm === "@ai-sdk/anthropic") { @@ -219,9 +243,9 @@ export namespace ProviderTransform { }) } - export function message(msgs: ModelMessage[], model: Provider.Model) { + export function message(msgs: ModelMessage[], model: Provider.Model, options: Record) { msgs = unsupportedParts(msgs, model) - msgs = normalizeMessages(msgs, model) + msgs = normalizeMessages(msgs, model, options) if ( model.providerID === "anthropic" || model.api.id.includes("anthropic") || diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index c8852edad0..7ad856bf0d 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -218,7 +218,7 @@ export namespace LLM { async transformParams(args) { if (args.type === "stream") { // @ts-expect-error - args.params.prompt = ProviderTransform.message(args.params.prompt, input.model) + args.params.prompt = ProviderTransform.message(args.params.prompt, input.model, options) } return args.params }, diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 61da69332e..6a28d77c93 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -261,40 +261,44 @@ describe("ProviderTransform.message - DeepSeek reasoning content", () => { }, ] as any[] - const result = ProviderTransform.message(msgs, { - id: "deepseek/deepseek-chat", - providerID: "deepseek", - api: { - id: "deepseek-chat", - url: "https://api.deepseek.com", - npm: "@ai-sdk/openai-compatible", - }, - name: "DeepSeek Chat", - capabilities: { - temperature: true, - reasoning: true, - attachment: false, - toolcall: true, - input: { text: true, audio: false, image: false, video: false, pdf: false }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: { - field: "reasoning_content", + const result = ProviderTransform.message( + msgs, + { + id: "deepseek/deepseek-chat", + providerID: "deepseek", + api: { + id: "deepseek-chat", + url: "https://api.deepseek.com", + npm: "@ai-sdk/openai-compatible", }, + name: "DeepSeek Chat", + capabilities: { + temperature: true, + reasoning: true, + attachment: false, + toolcall: true, + input: { text: true, audio: false, image: false, video: false, pdf: false }, + output: { text: true, audio: false, image: false, video: false, pdf: false }, + interleaved: { + field: "reasoning_content", + }, + }, + cost: { + input: 0.001, + output: 0.002, + cache: { read: 0.0001, write: 0.0002 }, + }, + limit: { + context: 128000, + output: 8192, + }, + status: "active", + options: {}, + headers: {}, + release_date: "2023-04-01", }, - cost: { - input: 0.001, - output: 0.002, - cache: { read: 0.0001, write: 0.0002 }, - }, - limit: { - context: 128000, - output: 8192, - }, - status: "active", - options: {}, - headers: {}, - release_date: "2023-04-01", - }) + {}, + ) expect(result).toHaveLength(1) expect(result[0].content).toEqual([ @@ -319,38 +323,42 @@ describe("ProviderTransform.message - DeepSeek reasoning content", () => { }, ] as any[] - const result = ProviderTransform.message(msgs, { - id: "openai/gpt-4", - providerID: "openai", - api: { - id: "gpt-4", - url: "https://api.openai.com", - npm: "@ai-sdk/openai", + const result = ProviderTransform.message( + msgs, + { + id: "openai/gpt-4", + providerID: "openai", + api: { + id: "gpt-4", + url: "https://api.openai.com", + npm: "@ai-sdk/openai", + }, + name: "GPT-4", + capabilities: { + temperature: true, + reasoning: false, + attachment: true, + toolcall: true, + input: { text: true, audio: false, image: true, video: false, pdf: false }, + output: { text: true, audio: false, image: false, video: false, pdf: false }, + interleaved: false, + }, + cost: { + input: 0.03, + output: 0.06, + cache: { read: 0.001, write: 0.002 }, + }, + limit: { + context: 128000, + output: 4096, + }, + status: "active", + options: {}, + headers: {}, + release_date: "2023-04-01", }, - name: "GPT-4", - capabilities: { - temperature: true, - reasoning: false, - attachment: true, - toolcall: true, - input: { text: true, audio: false, image: true, video: false, pdf: false }, - output: { text: true, audio: false, image: false, video: false, pdf: false }, - interleaved: false, - }, - cost: { - input: 0.03, - output: 0.06, - cache: { read: 0.001, write: 0.002 }, - }, - limit: { - context: 128000, - output: 4096, - }, - status: "active", - options: {}, - headers: {}, - release_date: "2023-04-01", - }) + {}, + ) expect(result[0].content).toEqual([ { type: "reasoning", text: "Should not be processed" }, @@ -404,7 +412,7 @@ describe("ProviderTransform.message - empty image handling", () => { }, ] as any[] - const result = ProviderTransform.message(msgs, mockModel) + const result = ProviderTransform.message(msgs, mockModel, {}) expect(result).toHaveLength(1) expect(result[0].content).toHaveLength(2) @@ -428,7 +436,7 @@ describe("ProviderTransform.message - empty image handling", () => { }, ] as any[] - const result = ProviderTransform.message(msgs, mockModel) + const result = ProviderTransform.message(msgs, mockModel, {}) expect(result).toHaveLength(1) expect(result[0].content).toHaveLength(2) @@ -450,7 +458,7 @@ describe("ProviderTransform.message - empty image handling", () => { }, ] as any[] - const result = ProviderTransform.message(msgs, mockModel) + const result = ProviderTransform.message(msgs, mockModel, {}) expect(result).toHaveLength(1) expect(result[0].content).toHaveLength(3) @@ -503,7 +511,7 @@ describe("ProviderTransform.message - anthropic empty content filtering", () => { role: "user", content: "World" }, ] as any[] - const result = ProviderTransform.message(msgs, anthropicModel) + const result = ProviderTransform.message(msgs, anthropicModel, {}) expect(result).toHaveLength(2) expect(result[0].content).toBe("Hello") @@ -522,7 +530,7 @@ describe("ProviderTransform.message - anthropic empty content filtering", () => }, ] as any[] - const result = ProviderTransform.message(msgs, anthropicModel) + const result = ProviderTransform.message(msgs, anthropicModel, {}) expect(result).toHaveLength(1) expect(result[0].content).toHaveLength(1) @@ -541,7 +549,7 @@ describe("ProviderTransform.message - anthropic empty content filtering", () => }, ] as any[] - const result = ProviderTransform.message(msgs, anthropicModel) + const result = ProviderTransform.message(msgs, anthropicModel, {}) expect(result).toHaveLength(1) expect(result[0].content).toHaveLength(1) @@ -561,7 +569,7 @@ describe("ProviderTransform.message - anthropic empty content filtering", () => { role: "user", content: "World" }, ] as any[] - const result = ProviderTransform.message(msgs, anthropicModel) + const result = ProviderTransform.message(msgs, anthropicModel, {}) expect(result).toHaveLength(2) expect(result[0].content).toBe("Hello") @@ -579,7 +587,7 @@ describe("ProviderTransform.message - anthropic empty content filtering", () => }, ] as any[] - const result = ProviderTransform.message(msgs, anthropicModel) + const result = ProviderTransform.message(msgs, anthropicModel, {}) expect(result).toHaveLength(1) expect(result[0].content).toHaveLength(1) @@ -603,7 +611,7 @@ describe("ProviderTransform.message - anthropic empty content filtering", () => }, ] as any[] - const result = ProviderTransform.message(msgs, anthropicModel) + const result = ProviderTransform.message(msgs, anthropicModel, {}) expect(result).toHaveLength(1) expect(result[0].content).toHaveLength(2) @@ -630,7 +638,7 @@ describe("ProviderTransform.message - anthropic empty content filtering", () => }, ] as any[] - const result = ProviderTransform.message(msgs, openaiModel) + const result = ProviderTransform.message(msgs, openaiModel, {}) expect(result).toHaveLength(2) expect(result[0].content).toBe("") @@ -638,6 +646,189 @@ describe("ProviderTransform.message - anthropic empty content filtering", () => }) }) +describe("ProviderTransform.message - strip openai metadata when store=false", () => { + const openaiModel = { + id: "openai/gpt-5", + providerID: "openai", + api: { + id: "gpt-5", + url: "https://api.openai.com", + npm: "@ai-sdk/openai", + }, + name: "GPT-5", + capabilities: { + temperature: true, + reasoning: true, + attachment: true, + toolcall: true, + input: { text: true, audio: false, image: true, video: false, pdf: false }, + output: { text: true, audio: false, image: false, video: false, pdf: false }, + interleaved: false, + }, + cost: { input: 0.03, output: 0.06, cache: { read: 0.001, write: 0.002 } }, + limit: { context: 128000, output: 4096 }, + status: "active", + options: {}, + headers: {}, + } as any + + test("strips itemId and reasoningEncryptedContent when store=false", () => { + const msgs = [ + { + role: "assistant", + content: [ + { + type: "reasoning", + text: "thinking...", + providerOptions: { + openai: { + itemId: "rs_123", + reasoningEncryptedContent: "encrypted", + }, + }, + }, + { + type: "text", + text: "Hello", + providerOptions: { + openai: { + itemId: "msg_456", + }, + }, + }, + ], + }, + ] as any[] + + const result = ProviderTransform.message(msgs, openaiModel, { store: false }) as any[] + + expect(result).toHaveLength(1) + expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined() + expect(result[0].content[0].providerOptions?.openai?.reasoningEncryptedContent).toBeUndefined() + expect(result[0].content[1].providerOptions?.openai?.itemId).toBeUndefined() + }) + + test("strips itemId and reasoningEncryptedContent when store=false even when not openai", () => { + const zenModel = { + ...openaiModel, + providerID: "zen", + } + const msgs = [ + { + role: "assistant", + content: [ + { + type: "reasoning", + text: "thinking...", + providerOptions: { + openai: { + itemId: "rs_123", + reasoningEncryptedContent: "encrypted", + }, + }, + }, + { + type: "text", + text: "Hello", + providerOptions: { + openai: { + itemId: "msg_456", + }, + }, + }, + ], + }, + ] as any[] + + const result = ProviderTransform.message(msgs, zenModel, { store: false }) as any[] + + expect(result).toHaveLength(1) + expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined() + expect(result[0].content[0].providerOptions?.openai?.reasoningEncryptedContent).toBeUndefined() + expect(result[0].content[1].providerOptions?.openai?.itemId).toBeUndefined() + }) + + test("preserves other openai options when stripping itemId", () => { + const msgs = [ + { + role: "assistant", + content: [ + { + type: "text", + text: "Hello", + providerOptions: { + openai: { + itemId: "msg_123", + otherOption: "value", + }, + }, + }, + ], + }, + ] as any[] + + const result = ProviderTransform.message(msgs, openaiModel, { store: false }) as any[] + + expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined() + expect(result[0].content[0].providerOptions?.openai?.otherOption).toBe("value") + }) + + test("does not strip metadata when store is not false", () => { + const msgs = [ + { + role: "assistant", + content: [ + { + type: "text", + text: "Hello", + providerOptions: { + openai: { + itemId: "msg_123", + }, + }, + }, + ], + }, + ] as any[] + + const result = ProviderTransform.message(msgs, openaiModel, { store: true }) as any[] + + expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123") + }) + + test("does not strip metadata for non-openai packages", () => { + const anthropicModel = { + ...openaiModel, + providerID: "anthropic", + api: { + id: "claude-3", + url: "https://api.anthropic.com", + npm: "@ai-sdk/anthropic", + }, + } + const msgs = [ + { + role: "assistant", + content: [ + { + type: "text", + text: "Hello", + providerOptions: { + openai: { + itemId: "msg_123", + }, + }, + }, + ], + }, + ] as any[] + + const result = ProviderTransform.message(msgs, anthropicModel, { store: false }) as any[] + + expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123") + }) +}) + describe("ProviderTransform.variants", () => { const createMockModel = (overrides: Partial = {}): any => ({ id: "test/test-model", From ce4b246cc0f6caccc97f41f74b5055b7de22799c Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 15 Jan 2026 00:19:44 -0600 Subject: [PATCH 3/5] Delete session.json --- session.json | 348 --------------------------------------------------- 1 file changed, 348 deletions(-) delete mode 100644 session.json diff --git a/session.json b/session.json deleted file mode 100644 index 5d1e5c2789..0000000000 --- a/session.json +++ /dev/null @@ -1,348 +0,0 @@ -{ - "info": { - "id": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "slug": "glowing-garden", - "version": "0.0.0-dev-202601142035", - "projectID": "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750", - "directory": "/Users/aidencline/development/opencode", - "title": "Greeting catch-up chat", - "time": { - "created": 1768423574827, - "updated": 1768423593422 - }, - "summary": { - "additions": 0, - "deletions": 0, - "files": 0 - } - }, - "messages": [ - { - "info": { - "id": "msg_bbe424d2f00120ijb14FQCGNc3", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "role": "user", - "time": { - "created": 1768423574839 - }, - "summary": { - "title": "Simple greeting note: Helloo", - "diffs": [] - }, - "agent": "build", - "model": { - "providerID": "openai", - "modelID": "gpt-5.2-codex" - }, - "variant": "high" - }, - "parts": [ - { - "id": "prt_bbe424d2f002jyG27z5VzBMKwa", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe424d2f00120ijb14FQCGNc3", - "type": "text", - "text": "Helloo" - } - ] - }, - { - "info": { - "id": "msg_bbe424d42001DteS1qORhxp6fn", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "role": "assistant", - "time": { - "created": 1768423574850, - "completed": 1768423579198 - }, - "parentID": "msg_bbe424d2f00120ijb14FQCGNc3", - "modelID": "gpt-5.2-codex", - "providerID": "openai", - "mode": "build", - "agent": "build", - "path": { - "cwd": "/Users/aidencline/development/opencode", - "root": "/Users/aidencline/development/opencode" - }, - "cost": 0.026537, - "tokens": { - "input": 15052, - "output": 14, - "reasoning": 0, - "cache": { - "read": 0, - "write": 0 - } - }, - "finish": "stop" - }, - "parts": [ - { - "id": "prt_bbe425dfe001tylFeRhFVxRiIV", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe424d42001DteS1qORhxp6fn", - "type": "step-start", - "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f" - }, - { - "id": "prt_bbe425dff001fd7GPeD22KjSWZ", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe424d42001DteS1qORhxp6fn", - "type": "reasoning", - "text": "", - "metadata": { - "openai": { - "itemId": "rs_0f251e66abfc3a1e006968009a62b0819c818ef7a34b35e5f1", - "reasoningEncryptedContent": "gAAAAABpaACafTEVzjtu8JssF7-OS2XXWeFodFFRHIGNQslmX51SchUx8p31PFmEGg_NNQ0O1r-WX10n_P9PZnl_EBgwLZDJUChT784MLS_pp3PyYc3HC6-eNRoDsEnzrkiUTWBiNx-U4Yq6yDriXTCMBkPjPUpSljgwQh3bT31a7pZ8PWfO2KG56zBKFljWb0UnLJjDiDL0MjNnVtb5JqZWQ8hWXUuv8o4LlYy-WqbN_Phj-gscnOd0ODbnrO6O9SDzre78Y7TwqCtqLi0bhjINViqkX72zK66-Rz8mGxmfNTCKFrVd5WUUUeM9w3SuYNRqJCyACZlMG2BPvhHpQDA2ouuCrFMHeP643hv9rSL7cz1krV0OANZTP1T_T8Pby9I9_8jZRXGIevFC6dEg_BJPDWjW3mxCN837tTuyaNb5JKCA8WPKktdp5L45UP5O5Sf3BmVUiKlRajzcnpmWCMoSzhR92YS-hZ9V6MvbclVZTol0XxXCFMw5XTx8PfWGDohnhp0UMGEqV3Dvo-__PTZITdf18tDj9ZvhMcvsYjvmoYFyw_8zXQhdT7EPqSPqdZqgqPVHR8BQNtZWTSPg3LhpCHYVPe9omf-63P4P34SWxuTOozLni-fFA2WawOCpDzbrtCLvaTrWKqKoOnxaLv3SdgV0ByggVNn-lSBnltKTzQKn3Cj_QfyV4o4UPlHi8JjbpMCpNITybbNuzIcP6jP1vWfDHqKC7Jk86AQGss2JDPuRfYdyYXfEkTPSCdgXEskgxAAvDufPPdc4AI7hTlnzhanqbUseKoKvxZrpU_KU-fB8HpM9T-_4ErMbvOgMkp7RPiS3GybBxCFgBBbhBCZhm_pCFxoTgjXEGa8iRTgZbeh5dBNVHk4=" - } - }, - "time": { - "start": 1768423579135, - "end": 1768423579135 - } - }, - { - "id": "prt_bbe425e00001V1yaBsuCYdy2kt", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe424d42001DteS1qORhxp6fn", - "type": "text", - "text": "Hey! How can I help today?", - "time": { - "start": 1768423579137, - "end": 1768423579137 - }, - "metadata": { - "openai": { - "itemId": "msg_0f251e66abfc3a1e006968009ae7f0819cb7e35354587c5ed9" - } - } - }, - { - "id": "prt_bbe425e02001wamNXw8crQaxwz", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe424d42001DteS1qORhxp6fn", - "type": "step-finish", - "reason": "stop", - "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f", - "cost": 0.026537, - "tokens": { - "input": 15052, - "output": 14, - "reasoning": 0, - "cache": { - "read": 0, - "write": 0 - } - } - } - ] - }, - { - "info": { - "id": "msg_bbe4272b3001VTE4T9MRZ9cVgd", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "role": "user", - "time": { - "created": 1768423584440 - }, - "summary": { - "title": "README explanation and project overview", - "diffs": [] - }, - "agent": "build", - "model": { - "providerID": "openai", - "modelID": "gpt-5.2-codex" - }, - "variant": "high" - }, - "parts": [ - { - "id": "prt_bbe4272b4001sm75kVdmwwc7N0", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe4272b3001VTE4T9MRZ9cVgd", - "type": "text", - "text": "read my readme and explain project" - } - ] - }, - { - "info": { - "id": "msg_bbe4272bd001zTvdwgAG7xiaI9", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "role": "assistant", - "time": { - "created": 1768423584445, - "completed": 1768423589447 - }, - "parentID": "msg_bbe4272b3001VTE4T9MRZ9cVgd", - "modelID": "gpt-5.2-codex", - "providerID": "openai", - "mode": "build", - "agent": "build", - "path": { - "cwd": "/Users/aidencline/development/opencode", - "root": "/Users/aidencline/development/opencode" - }, - "cost": 0.00323155, - "tokens": { - "input": 101, - "output": 31, - "reasoning": 0, - "cache": { - "read": 14976, - "write": 0 - } - }, - "finish": "tool-calls" - }, - "parts": [ - { - "id": "prt_bbe42860700166hdZ87CN0RnGX", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe4272bd001zTvdwgAG7xiaI9", - "type": "step-start", - "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f" - }, - { - "id": "prt_bbe4286080012Nzn3hPheDenwl", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe4272bd001zTvdwgAG7xiaI9", - "type": "reasoning", - "text": "**Planning README read**\n\nI need to read the README file and am considering using a direct path or glob to locate it, favoring reading /Users/aidencline/development/opencode/README.md.", - "metadata": { - "openai": { - "itemId": "rs_0f251e66abfc3a1e00696800a2ffa8819c8e428e32a0924bbe", - "reasoningEncryptedContent": "gAAAAABpaAClTxa2kLNStT-_Bb8iu3y5mNhaeLnCLtPi4WmNqaPUWUl36OgxVfHZ-IMi1S8TAVqeLDpuMTehuTjfmqeyii1YFR8SxlhOhKeSLZH9_eBH7XMlHFndIp-i8oM0w_QkKPPal-yQqbHd4xcgS6goX2rr7XOOpOTcX_UN2RpvQxpJa05RkbHkX2kp2_IlM0dSma7Kd9IdPsM4mYMd2RqHsn6VnUj41oL7ptcSfaIIhJS0y7JBlCi8AMxF5Z0uBeVhp_HLN4ZqfziPY_XtOrPncyBd3IwWGg-mMpq26qj03PyncgJ3WPtyIr9O-r0NHWJKtis6dcOWOem-RpJ8f9ZvMqoRlA7C2pRMP0L58sc52P9QFCyiTKs37zZ5VByFzKcgBtSD_YHvu5xWDEn9D7QEy68tjcaseL41312l_wlLpmuPjLvMyPkxAr_LP4-EJW04WHWTkObfggZu45snu6jQZm56WWa9ijLeD4d4CAFG-JNfWb-7hrIt8ZYIiS5m3TE9bMy78el2cFkior5AZplMQoo2gXhx9Q8XN9w6xFLoGLWwCtfX-Nm8tmeMDGdDStk46j3zABgS_W2dxD-auJ6rD2CDJQyzoPm-emA-KABpSm3I0XhDe4zKpRzbwPjvL9mtYbkXG4cG3tiK5G2CZEXmyta-xeC8psWUSD-fbvEyc5mbfh6rkv9SFgKHeXVSsu04V8NbKe0Nz0XOxtS5m3sO1YPqAjnsHbVeUUQxg1N47J2FAN-qkJwoLlIeoy1MwDOlq9tYLAJQkC577BHa-apNH71MexgjDhFcanv3zq5UBdTxs-x6pDufa9HDf5qmWL2tzevy9rJleVnnMZ97aylY3Q2bsnGPQBuJM_HILVed3EUf2BI08yteVbTFgIA6hRSdilsNYt8cUHJfSR-zADyrKY4OLOJXIAQcApVSRXq0GzdjXKfKbLOpomdU7RoZK8zs19TqieLPfPfMmKjQ4W3Q6of0IEfargc6lngeQtSR05ijdtoePqwHej4jSuarEGJo4SlY84R8kQLx2ZMIUvWIUmUfcOjTVo7RhEXO5kW3KAa2ZZY=" - } - }, - "time": { - "start": 1768423589384, - "end": 1768423589390 - } - }, - { - "id": "prt_bbe42860e0017PEtVVfp8s4HxM", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe4272bd001zTvdwgAG7xiaI9", - "type": "tool", - "callID": "call_52Ssi7DCzmyClgs0Zt9Z1uMT", - "tool": "read", - "state": { - "status": "completed", - "input": { - "filePath": "/Users/aidencline/development/opencode/README.md" - }, - "output": "\n00001|

\n00002| \n00003| \n00004| \n00005| \n00006| \"OpenCode\n00007| \n00008| \n00009|

\n00010|

The open source AI coding agent.

\n00011|

\n00012| \"Discord\"\n00013| \"npm\"\n00014| \"Build\n00015|

\n00016| \n00017| [![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)\n00018| \n00019| ---\n00020| \n00021| ### Installation\n00022| \n00023| ```bash\n00024| # YOLO\n00025| curl -fsSL https://opencode.ai/install | bash\n00026| \n00027| # Package managers\n00028| npm i -g opencode-ai@latest # or bun/pnpm/yarn\n00029| scoop bucket add extras; scoop install extras/opencode # Windows\n00030| choco install opencode # Windows\n00031| brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)\n00032| brew install opencode # macOS and Linux (official brew formula, updated less)\n00033| paru -S opencode-bin # Arch Linux\n00034| mise use -g opencode # Any OS\n00035| nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch\n00036| ```\n00037| \n00038| > [!TIP]\n00039| > Remove versions older than 0.1.x before installing.\n00040| \n00041| ### Desktop App (BETA)\n00042| \n00043| OpenCode is also available as a desktop application. Download directly from the [releases page](https://github.com/anomalyco/opencode/releases) or [opencode.ai/download](https://opencode.ai/download).\n00044| \n00045| | Platform | Download |\n00046| | --------------------- | ------------------------------------- |\n00047| | macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |\n00048| | macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |\n00049| | Windows | `opencode-desktop-windows-x64.exe` |\n00050| | Linux | `.deb`, `.rpm`, or AppImage |\n00051| \n00052| ```bash\n00053| # macOS (Homebrew)\n00054| brew install --cask opencode-desktop\n00055| ```\n00056| \n00057| #### Installation Directory\n00058| \n00059| The install script respects the following priority order for the installation path:\n00060| \n00061| 1. `$OPENCODE_INSTALL_DIR` - Custom installation directory\n00062| 2. `$XDG_BIN_DIR` - XDG Base Directory Specification compliant path\n00063| 3. `$HOME/bin` - Standard user binary directory (if exists or can be created)\n00064| 4. `$HOME/.opencode/bin` - Default fallback\n00065| \n00066| ```bash\n00067| # Examples\n00068| OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash\n00069| XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash\n00070| ```\n00071| \n00072| ### Agents\n00073| \n00074| OpenCode includes two built-in agents you can switch between with the `Tab` key.\n00075| \n00076| - **build** - Default, full access agent for development work\n00077| - **plan** - Read-only agent for analysis and code exploration\n00078| - Denies file edits by default\n00079| - Asks permission before running bash commands\n00080| - Ideal for exploring unfamiliar codebases or planning changes\n00081| \n00082| Also, included is a **general** subagent for complex searches and multistep tasks.\n00083| This is used internally and can be invoked using `@general` in messages.\n00084| \n00085| Learn more about [agents](https://opencode.ai/docs/agents).\n00086| \n00087| ### Documentation\n00088| \n00089| For more info on how to configure OpenCode [**head over to our docs**](https://opencode.ai/docs).\n00090| \n00091| ### Contributing\n00092| \n00093| If you're interested in contributing to OpenCode, please read our [contributing docs](./CONTRIBUTING.md) before submitting a pull request.\n00094| \n00095| ### Building on OpenCode\n00096| \n00097| If you are working on a project that's related to OpenCode and is using \"opencode\" as a part of its name; for example, \"opencode-dashboard\" or \"opencode-mobile\", please add a note to your README to clarify that it is not built by the OpenCode team and is not affiliated with us in any way.\n00098| \n00099| ### FAQ\n00100| \n00101| #### How is this different from Claude Code?\n00102| \n00103| It's very similar to Claude Code in terms of capability. Here are the key differences:\n00104| \n00105| - 100% open source\n00106| - Not coupled to any provider. Although we recommend the models we provide through [OpenCode Zen](https://opencode.ai/zen); OpenCode can be used with Claude, OpenAI, Google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider-agnostic is important.\n00107| - Out of the box LSP support\n00108| - A focus on TUI. OpenCode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.\n00109| - A client/server architecture. This for example can allow OpenCode to run on your computer, while you can drive it remotely from a mobile app. Meaning that the TUI frontend is just one of the possible clients.\n00110| \n00111| ---\n00112| \n00113| **Join our community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)\n00114| \n\n(End of file - total 114 lines)\n
", - "title": "README.md", - "metadata": { - "preview": "

\n \n \n \n \n \"OpenCode\n \n \n

\n

The open source AI coding agent.

\n

\n \"Discord\"\n \"npm\"\n \"Build\n

\n\n[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)\n\n---\n", - "truncated": false - }, - "time": { - "start": 1768423589391, - "end": 1768423589392 - } - }, - "metadata": { - "openai": { - "itemId": "fc_0f251e66abfc3a1e00696800a514d0819ca3bdcb6deb51d375" - } - } - }, - { - "id": "prt_bbe428611001gpVltZwFRQFvVV", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe4272bd001zTvdwgAG7xiaI9", - "type": "step-finish", - "reason": "tool-calls", - "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f", - "cost": 0.00323155, - "tokens": { - "input": 101, - "output": 31, - "reasoning": 0, - "cache": { - "read": 14976, - "write": 0 - } - } - } - ] - }, - { - "info": { - "id": "msg_bbe42864a001SoQKUk5dtIu7wb", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "role": "assistant", - "time": { - "created": 1768423589450, - "completed": 1768423593413 - }, - "parentID": "msg_bbe4272b3001VTE4T9MRZ9cVgd", - "modelID": "gpt-5.2-codex", - "providerID": "openai", - "mode": "build", - "agent": "build", - "path": { - "cwd": "/Users/aidencline/development/opencode", - "root": "/Users/aidencline/development/opencode" - }, - "cost": 0.0076643, - "tokens": { - "input": 1866, - "output": 127, - "reasoning": 0, - "cache": { - "read": 14976, - "write": 0 - } - }, - "finish": "stop" - }, - "parts": [ - { - "id": "prt_bbe42957c001G2nL0DDJY7Q80W", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe42864a001SoQKUk5dtIu7wb", - "type": "step-start", - "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f" - }, - { - "id": "prt_bbe42957d001neCElzalxzFjRv", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe42864a001SoQKUk5dtIu7wb", - "type": "text", - "text": "Here’s a quick overview of the project based on `README.md`:\n\n**Summary**\n- OpenCode is an open‑source AI coding agent focused on a terminal UI experience.\n- It ships as a CLI and a desktop app, with multiple install options across platforms.\n- It supports multiple model providers and emphasizes provider‑agnostic workflows.\n- It includes built‑in agents (build/plan) and a general subagent for complex tasks.\n- It uses a client/server architecture for flexible frontends beyond the TUI.\n\nWant a deeper walkthrough of specific parts (CLI, agents, SDKs, desktop app)?", - "time": { - "start": 1768423593356, - "end": 1768423593356 - }, - "metadata": { - "openai": { - "itemId": "msg_0f251e66abfc3a1e00696800a6e2f4819c9bff0c98e6a5ce3b" - } - } - }, - { - "id": "prt_bbe42958d001GOD7AK1WFYTGzO", - "sessionID": "ses_441bdb2d4ffeUrukcqQcQt57q0", - "messageID": "msg_bbe42864a001SoQKUk5dtIu7wb", - "type": "step-finish", - "reason": "stop", - "snapshot": "92589bb4f79eff37fc4e6d25d4b6794cf01c956f", - "cost": 0.0076643, - "tokens": { - "input": 1866, - "output": 127, - "reasoning": 0, - "cache": { - "read": 14976, - "write": 0 - } - } - } - ] - } - ] -} From 4ce5ee161a71d88a665d0c95d9ddc377f919e166 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 15 Jan 2026 01:15:03 -0600 Subject: [PATCH 4/5] tweak: transform --- packages/opencode/src/provider/transform.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index e49aaf875b..349e579e03 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -22,8 +22,8 @@ export namespace ProviderTransform { model: Provider.Model, options: Record, ): ModelMessage[] { - // Strip openai itemId metadata when store=false to avoid sending stale IDs - if (model.api.npm === "@ai-sdk/openai" && options.store === false) { + // Strip openai itemId metadata following what codex does + if (model.api.npm === "@ai-sdk/openai" || options.store === false) { msgs = msgs.map((msg) => { if (!Array.isArray(msg.content)) return msg const content = msg.content.map((part) => { @@ -486,8 +486,8 @@ export namespace ProviderTransform { }): Record { const result: Record = {} - // all codex plans MUST use store = false - if (input.model.providerID === "openai" && input.auth?.type === "oauth") { + // openai and providers using openai package should set store to false by default. + if (input.model.providerID === "openai" || input.model.api.npm === "@ai-sdk/openai") { result["store"] = false } From bab5b318dacf71a282b5608cacae7e6c7b610554 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Thu, 15 Jan 2026 01:28:09 -0600 Subject: [PATCH 5/5] fix: tests --- packages/opencode/src/provider/transform.ts | 2 - packages/opencode/src/session/llm.ts | 1 - .../opencode/test/provider/transform.test.ts | 69 +++++++++++-------- 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 349e579e03..d1bef787ec 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -3,7 +3,6 @@ import { unique } from "remeda" import type { JSONSchema } from "zod/v4/core" import type { Provider } from "./provider" import type { ModelsDev } from "./models" -import type { Auth } from "@/auth" import { iife } from "@/util/iife" type Modality = NonNullable["input"][number] @@ -482,7 +481,6 @@ export namespace ProviderTransform { model: Provider.Model sessionID: string providerOptions?: Record - auth: Auth.Info | undefined }): Record { const result: Record = {} diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 7ad856bf0d..5b6178bc01 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -99,7 +99,6 @@ export namespace LLM { model: input.model, sessionID: input.sessionID, providerOptions: provider.options, - auth, }) const options: Record = pipe( base, diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 6a28d77c93..3814e9d99f 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -43,7 +43,6 @@ describe("ProviderTransform.options - setCacheKey", () => { model: mockModel, sessionID, providerOptions: { setCacheKey: true }, - auth: undefined, }) expect(result.promptCacheKey).toBe(sessionID) }) @@ -53,7 +52,6 @@ describe("ProviderTransform.options - setCacheKey", () => { model: mockModel, sessionID, providerOptions: { setCacheKey: false }, - auth: undefined, }) expect(result.promptCacheKey).toBeUndefined() }) @@ -63,13 +61,12 @@ describe("ProviderTransform.options - setCacheKey", () => { model: mockModel, sessionID, providerOptions: undefined, - auth: undefined, }) expect(result.promptCacheKey).toBeUndefined() }) test("should not set promptCacheKey when providerOptions does not have setCacheKey", () => { - const result = ProviderTransform.options({ model: mockModel, sessionID, providerOptions: {}, auth: undefined }) + const result = ProviderTransform.options({ model: mockModel, sessionID, providerOptions: {} }) expect(result.promptCacheKey).toBeUndefined() }) @@ -83,11 +80,11 @@ describe("ProviderTransform.options - setCacheKey", () => { npm: "@ai-sdk/openai", }, } - const result = ProviderTransform.options({ model: openaiModel, sessionID, providerOptions: {}, auth: undefined }) + const result = ProviderTransform.options({ model: openaiModel, sessionID, providerOptions: {} }) expect(result.promptCacheKey).toBe(sessionID) }) - test("should set store=false for openai provider with oauth auth", () => { + test("should set store=false for openai provider", () => { const openaiModel = { ...mockModel, providerID: "openai", @@ -101,29 +98,9 @@ describe("ProviderTransform.options - setCacheKey", () => { model: openaiModel, sessionID, providerOptions: {}, - auth: { type: "oauth", refresh: "r", access: "a", expires: 0 }, }) expect(result.store).toBe(false) }) - - test("should not set store=false for openai provider with api auth", () => { - const openaiModel = { - ...mockModel, - providerID: "openai", - api: { - id: "gpt-4", - url: "https://api.openai.com", - npm: "@ai-sdk/openai", - }, - } - const result = ProviderTransform.options({ - model: openaiModel, - sessionID, - providerOptions: {}, - auth: { type: "api", key: "sk-xxx" }, - }) - expect(result.store).toBeUndefined() - }) }) describe("ProviderTransform.maxOutputTokens", () => { @@ -773,7 +750,7 @@ describe("ProviderTransform.message - strip openai metadata when store=false", ( expect(result[0].content[0].providerOptions?.openai?.otherOption).toBe("value") }) - test("does not strip metadata when store is not false", () => { + test("strips metadata for openai package even when store is true", () => { const msgs = [ { role: "assistant", @@ -791,12 +768,13 @@ describe("ProviderTransform.message - strip openai metadata when store=false", ( }, ] as any[] + // openai package always strips itemId regardless of store value const result = ProviderTransform.message(msgs, openaiModel, { store: true }) as any[] - expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123") + expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined() }) - test("does not strip metadata for non-openai packages", () => { + test("strips metadata for non-openai packages when store is false", () => { const anthropicModel = { ...openaiModel, providerID: "anthropic", @@ -823,8 +801,41 @@ describe("ProviderTransform.message - strip openai metadata when store=false", ( }, ] as any[] + // store=false triggers stripping even for non-openai packages const result = ProviderTransform.message(msgs, anthropicModel, { store: false }) as any[] + expect(result[0].content[0].providerOptions?.openai?.itemId).toBeUndefined() + }) + + test("does not strip metadata for non-openai packages when store is not false", () => { + const anthropicModel = { + ...openaiModel, + providerID: "anthropic", + api: { + id: "claude-3", + url: "https://api.anthropic.com", + npm: "@ai-sdk/anthropic", + }, + } + const msgs = [ + { + role: "assistant", + content: [ + { + type: "text", + text: "Hello", + providerOptions: { + openai: { + itemId: "msg_123", + }, + }, + }, + ], + }, + ] as any[] + + const result = ProviderTransform.message(msgs, anthropicModel, {}) as any[] + expect(result[0].content[0].providerOptions?.openai?.itemId).toBe("msg_123") }) })