From c1217a9861900d8cc3b557554f3f7625b4b27352 Mon Sep 17 00:00:00 2001 From: Michael Hart Date: Fri, 20 Feb 2026 10:53:52 +1100 Subject: [PATCH 1/3] Use structuredClone instead of remeda's clone Avoids pathological cases when cloning large objects Fixes #14344 --- .../opencode/src/cli/cmd/tui/component/prompt/history.tsx | 3 +-- packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx | 3 +-- packages/opencode/src/session/llm.ts | 4 ++-- packages/opencode/src/session/prompt.ts | 3 +-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx index c40534e7ec20..c1198039d66e 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx @@ -3,7 +3,6 @@ import { Global } from "@/global" import { Filesystem } from "@/util/filesystem" import { onMount } from "solid-js" import { createStore, produce } from "solid-js/store" -import { clone } from "remeda" import { createSimpleContext } from "../../context/helper" import { appendFile, writeFile } from "fs/promises" import type { AgentPart, FilePart, TextPart } from "@opencode-ai/sdk/v2" @@ -83,7 +82,7 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create return store.history.at(store.index) }, append(item: PromptInfo) { - const entry = clone(item) + const entry = structuredClone(item) let trimmed = false setStore( produce((draft) => { diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx index d4dc138d893a..4a831b2735b1 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx @@ -3,7 +3,6 @@ import { Global } from "@/global" import { Filesystem } from "@/util/filesystem" import { onMount } from "solid-js" import { createStore, produce } from "solid-js/store" -import { clone } from "remeda" import { createSimpleContext } from "../../context/helper" import { appendFile, writeFile } from "fs/promises" import type { PromptInfo } from "./history" @@ -53,7 +52,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp return store.entries }, push(entry: Omit) { - const stash = clone({ ...entry, timestamp: Date.now() }) + const stash = structuredClone({ ...entry, timestamp: Date.now() }) let trimmed = false setStore( produce((draft) => { diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index fa880391276c..53d8fdc1d3a4 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -11,7 +11,7 @@ import { tool, jsonSchema, } from "ai" -import { clone, mergeDeep, pipe } from "remeda" +import { mergeDeep, pipe } from "remeda" import { ProviderTransform } from "@/provider/transform" import { Config } from "@/config/config" import { Instance } from "@/project/instance" @@ -80,7 +80,7 @@ export namespace LLM { ) const header = system[0] - const original = clone(system) + const original = structuredClone(system) await Plugin.trigger( "experimental.chat.system.transform", { sessionID: input.sessionID, model: input.model }, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 6ca93979e306..bf7e16f62274 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -22,7 +22,6 @@ import PROMPT_PLAN from "../session/prompt/plan.txt" import BUILD_SWITCH from "../session/prompt/build-switch.txt" import MAX_STEPS from "../session/prompt/max-steps.txt" import { defer } from "../util/defer" -import { clone } from "remeda" import { ToolRegistry } from "../tool/registry" import { MCP } from "../mcp" import { LSP } from "../lsp" @@ -627,7 +626,7 @@ export namespace SessionPrompt { }) } - const sessionMessages = clone(msgs) + const sessionMessages = structuredClone(msgs) // Ephemerally wrap queued user messages with a reminder to stay on track if (step > 1 && lastFinished) { From 9075d865c9478031b2f55c1d334c2a5d3558dcdf Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 19 Feb 2026 19:16:36 -0500 Subject: [PATCH 2/3] revert backend changes, keep only frontend clone fixes --- packages/opencode/src/session/llm.ts | 22 ++++++++-------------- packages/opencode/src/session/prompt.ts | 3 ++- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 53d8fdc1d3a4..e7e7e1d700c5 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -11,7 +11,7 @@ import { tool, jsonSchema, } from "ai" -import { mergeDeep, pipe } from "remeda" +import { clone, mergeDeep, pipe } from "remeda" import { ProviderTransform } from "@/provider/transform" import { Config } from "@/config/config" import { Instance } from "@/project/instance" @@ -80,7 +80,7 @@ export namespace LLM { ) const header = system[0] - const original = structuredClone(system) + const original = clone(system) await Plugin.trigger( "experimental.chat.system.transform", { sessionID: input.sessionID, model: input.model }, @@ -210,18 +210,12 @@ export namespace LLM { maxOutputTokens, abortSignal: input.abort, headers: { - ...(input.model.providerID.startsWith("opencode") - ? { - "x-opencode-project": Instance.project.id, - "x-opencode-session": input.sessionID, - "x-opencode-request": input.user.id, - "x-opencode-client": Flag.OPENCODE_CLIENT, - } - : input.model.providerID !== "anthropic" - ? { - "User-Agent": `opencode/${Installation.VERSION}`, - } - : undefined), + ...(input.model.providerID.startsWith("opencode") && { + "x-opencode-project": Instance.project.id, + "x-opencode-session": input.sessionID, + "x-opencode-request": input.user.id, + "x-opencode-client": Flag.OPENCODE_CLIENT, + }), ...input.model.headers, ...headers, }, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index bf7e16f62274..6ca93979e306 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -22,6 +22,7 @@ import PROMPT_PLAN from "../session/prompt/plan.txt" import BUILD_SWITCH from "../session/prompt/build-switch.txt" import MAX_STEPS from "../session/prompt/max-steps.txt" import { defer } from "../util/defer" +import { clone } from "remeda" import { ToolRegistry } from "../tool/registry" import { MCP } from "../mcp" import { LSP } from "../lsp" @@ -626,7 +627,7 @@ export namespace SessionPrompt { }) } - const sessionMessages = structuredClone(msgs) + const sessionMessages = clone(msgs) // Ephemerally wrap queued user messages with a reminder to stay on track if (step > 1 && lastFinished) { From 736a30f3617a7617307c7fdf2c9b160469b70938 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 19 Feb 2026 19:18:04 -0500 Subject: [PATCH 3/3] restore llm.ts to match PR base --- packages/opencode/src/session/llm.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index e7e7e1d700c5..fa880391276c 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -210,12 +210,18 @@ export namespace LLM { maxOutputTokens, abortSignal: input.abort, headers: { - ...(input.model.providerID.startsWith("opencode") && { - "x-opencode-project": Instance.project.id, - "x-opencode-session": input.sessionID, - "x-opencode-request": input.user.id, - "x-opencode-client": Flag.OPENCODE_CLIENT, - }), + ...(input.model.providerID.startsWith("opencode") + ? { + "x-opencode-project": Instance.project.id, + "x-opencode-session": input.sessionID, + "x-opencode-request": input.user.id, + "x-opencode-client": Flag.OPENCODE_CLIENT, + } + : input.model.providerID !== "anthropic" + ? { + "User-Agent": `opencode/${Installation.VERSION}`, + } + : undefined), ...input.model.headers, ...headers, },