From 912098928a2c77537a632aee841daf95c6971e04 Mon Sep 17 00:00:00 2001 From: Dave Dennis <99584622+0xdsqr@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:22:50 -0600 Subject: [PATCH 01/59] =?UTF-8?q?fix(app):=20derive=20terminal=20WebSocket?= =?UTF-8?q?=20URL=20from=20browser=20origin=20instead=20o=E2=80=A6=20(#121?= =?UTF-8?q?78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/src/components/terminal-url.test.ts | 36 +++++++++++++++++++ packages/app/src/components/terminal-url.ts | 10 ++++++ packages/app/src/components/terminal.tsx | 4 +-- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 packages/app/src/components/terminal-url.test.ts create mode 100644 packages/app/src/components/terminal-url.ts diff --git a/packages/app/src/components/terminal-url.test.ts b/packages/app/src/components/terminal-url.test.ts new file mode 100644 index 0000000000..c07e3fa950 --- /dev/null +++ b/packages/app/src/components/terminal-url.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from "bun:test" +import { ptySocketUrl } from "./terminal-url" + +describe("ptySocketUrl", () => { + test("uses browser host instead of sdk host", () => { + const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { + host: "192.168.1.50:4096", + protocol: "http:", + }) + expect(url.toString()).toBe("ws://192.168.1.50:4096/pty/pty_1/connect?directory=%2Frepo") + }) + + test("uses secure websocket on https", () => { + const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { + host: "opencode.local", + protocol: "https:", + }) + expect(url.toString()).toBe("wss://opencode.local/pty/pty_1/connect?directory=%2Frepo") + }) + + test("preserves browser port", () => { + const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { + host: "opencode.local:8443", + protocol: "https:", + }) + expect(url.toString()).toBe("wss://opencode.local:8443/pty/pty_1/connect?directory=%2Frepo") + }) + + test("handles slash base url", () => { + const url = ptySocketUrl("/", "pty_1", "/repo", { + host: "192.168.1.50:4096", + protocol: "http:", + }) + expect(url.toString()).toBe("ws://192.168.1.50:4096/pty/pty_1/connect?directory=%2Frepo") + }) +}) diff --git a/packages/app/src/components/terminal-url.ts b/packages/app/src/components/terminal-url.ts new file mode 100644 index 0000000000..c2c9fb53c5 --- /dev/null +++ b/packages/app/src/components/terminal-url.ts @@ -0,0 +1,10 @@ +export function ptySocketUrl(base: string, id: string, directory: string, origin: { host: string; protocol: string }) { + const root = `${origin.protocol}//${origin.host}` + const current = new URL(root) + const prefix = /^https?:\/\//.test(base) ? base : new URL(base || "/", root).toString() + const url = new URL(prefix.replace(/\/+$/, "") + `/pty/${id}/connect?directory=${encodeURIComponent(directory)}`) + url.hostname = current.hostname + url.port = current.port + url.protocol = origin.protocol === "https:" ? "wss:" : "ws:" + return url +} diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index 4ad79ee82b..563418d601 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -8,6 +8,7 @@ import { LocalPTY } from "@/context/terminal" import { resolveThemeVariant, useTheme, withAlpha, type HexColor } from "@opencode-ai/ui/theme" import { useLanguage } from "@/context/language" import { showToast } from "@opencode-ai/ui/toast" +import { ptySocketUrl } from "./terminal-url" export interface TerminalProps extends ComponentProps<"div"> { pty: LocalPTY @@ -163,8 +164,7 @@ export const Terminal = (props: TerminalProps) => { const once = { value: false } - const url = new URL(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`) - url.protocol = url.protocol === "https:" ? "wss:" : "ws:" + const url = ptySocketUrl(sdk.url, local.pty.id, sdk.directory, window.location) if (window.__OPENCODE__?.serverPassword) { url.username = "opencode" url.password = window.__OPENCODE__?.serverPassword From 41bc4ec7f0a42d6350b2af5ad44c7a308ee26b25 Mon Sep 17 00:00:00 2001 From: Parker Smith Date: Wed, 4 Feb 2026 13:27:02 -0700 Subject: [PATCH 02/59] fix(desktop): Refresh file contents when changing workspaces to not have stale contents (#11728) --- packages/app/src/context/file.tsx | 7 ++++--- packages/app/src/pages/session.tsx | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/app/src/context/file.tsx b/packages/app/src/context/file.tsx index 7509334edb..d05e1ee2ad 100644 --- a/packages/app/src/context/file.tsx +++ b/packages/app/src/context/file.tsx @@ -8,6 +8,7 @@ import { getFilename } from "@opencode-ai/util/path" import { useSDK } from "./sdk" import { useSync } from "./sync" import { useLanguage } from "@/context/language" +import { decode64 } from "@/utils/base64" import { Persist, persisted } from "@/utils/persist" export type FileSelection = { @@ -275,9 +276,9 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({ const params = useParams() const language = useLanguage() - const scope = createMemo(() => sdk.directory) + const directory = createMemo(() => decode64(params.dir) ?? sdk.directory) - const directory = createMemo(() => sync.data.path.directory) + const scope = createMemo(() => directory()) function normalize(input: string) { const root = directory() @@ -414,7 +415,7 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({ return entry.value } - const view = createMemo(() => loadView(params.dir!, params.id)) + const view = createMemo(() => loadView(directory(), params.id)) function ensure(path: string) { if (!path) return diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 7f005c56e5..3fa71244bf 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1670,6 +1670,22 @@ export default function Page() { void (refresh ? file.tree.refresh("") : file.tree.list("")) }) + createEffect( + on( + () => params.dir, + () => { + void file.tree.list("") + + const active = tabs().active() + if (!active) return + const path = file.pathFromTab(active) + if (!path) return + void file.load(path, { force: true }) + }, + { defer: true }, + ), + ) + const autoScroll = createAutoScroll({ working: () => true, overflowAnchor: "dynamic", From 9679e0c59cd7682412e35046b0fd1754476aa5ec Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:35:47 -0600 Subject: [PATCH 03/59] fix(app): terminal EOL issues --- packages/app/src/components/terminal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index 563418d601..a6ce8acfa0 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -185,6 +185,7 @@ export const Terminal = (props: TerminalProps) => { fontSize: 14, fontFamily: monoFontFamily(settings.appearance.font()), allowTransparency: true, + convertEol: true, theme: terminalColors(), scrollback: 10_000, ghostty: g, From 0d38e69038c9a79e53159a747bf277748d5d79c5 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Wed, 4 Feb 2026 20:45:59 +0000 Subject: [PATCH 04/59] fix(core): skip dependency install in read-only config dirs (#12128) --- packages/opencode/src/config/config.ts | 19 +++++- packages/opencode/test/config/config.test.ts | 61 ++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 5fde2aed87..436e829839 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -24,7 +24,7 @@ import { LSPServer } from "../lsp/server" import { BunProc } from "@/bun" import { Installation } from "@/installation" import { ConfigMarkdown } from "./markdown" -import { existsSync } from "fs" +import { constants, existsSync } from "fs" import { Bus } from "@/bus" import { GlobalBus } from "@/bus/global" import { Event } from "../server/event" @@ -273,7 +273,24 @@ export namespace Config { ).catch(() => {}) } + async function isWritable(dir: string) { + try { + await fs.access(dir, constants.W_OK) + return true + } catch { + return false + } + } + async function needsInstall(dir: string) { + // Some config dirs may be read-only. + // Installing deps there will fail; skip installation in that case. + const writable = await isWritable(dir) + if (!writable) { + log.debug("config dir is not writable, skipping dependency install", { dir }) + return false + } + const nodeModules = path.join(dir, "node_modules") if (!existsSync(nodeModules)) return true diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 8611d82969..1014a49687 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -566,6 +566,67 @@ test("gets config directories", async () => { }) }) +test("does not try to install dependencies in read-only OPENCODE_CONFIG_DIR", async () => { + if (process.platform === "win32") return + + await using tmp = await tmpdir({ + init: async (dir) => { + const ro = path.join(dir, "readonly") + await fs.mkdir(ro, { recursive: true }) + await fs.chmod(ro, 0o555) + return ro + }, + dispose: async (dir) => { + const ro = path.join(dir, "readonly") + await fs.chmod(ro, 0o755).catch(() => {}) + return ro + }, + }) + + const prev = process.env.OPENCODE_CONFIG_DIR + process.env.OPENCODE_CONFIG_DIR = tmp.extra + + try { + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await Config.get() + }, + }) + } finally { + if (prev === undefined) delete process.env.OPENCODE_CONFIG_DIR + else process.env.OPENCODE_CONFIG_DIR = prev + } +}) + +test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + const cfg = path.join(dir, "configdir") + await fs.mkdir(cfg, { recursive: true }) + return cfg + }, + }) + + const prev = process.env.OPENCODE_CONFIG_DIR + process.env.OPENCODE_CONFIG_DIR = tmp.extra + + try { + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await Config.get() + }, + }) + + expect(await Bun.file(path.join(tmp.extra, "package.json")).exists()).toBe(true) + expect(await Bun.file(path.join(tmp.extra, ".gitignore")).exists()).toBe(true) + } finally { + if (prev === undefined) delete process.env.OPENCODE_CONFIG_DIR + else process.env.OPENCODE_CONFIG_DIR = prev + } +}) + test("resolves scoped npm plugins in config", async () => { await using tmp = await tmpdir({ init: async (dir) => { From 2896b8a863909f345468a959669f62cc7feca7c9 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:43:08 -0600 Subject: [PATCH 05/59] fix(app): terminal url --- .../app/src/components/terminal-url.test.ts | 26 ++++++++++++------- packages/app/src/components/terminal-url.ts | 14 +++++----- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/app/src/components/terminal-url.test.ts b/packages/app/src/components/terminal-url.test.ts index c07e3fa950..8548d70876 100644 --- a/packages/app/src/components/terminal-url.test.ts +++ b/packages/app/src/components/terminal-url.test.ts @@ -2,26 +2,34 @@ import { describe, expect, test } from "bun:test" import { ptySocketUrl } from "./terminal-url" describe("ptySocketUrl", () => { - test("uses browser host instead of sdk host", () => { + test("uses sdk host for absolute server url", () => { const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { host: "192.168.1.50:4096", protocol: "http:", }) - expect(url.toString()).toBe("ws://192.168.1.50:4096/pty/pty_1/connect?directory=%2Frepo") + expect(url.toString()).toBe("ws://localhost:4096/pty/pty_1/connect?directory=%2Frepo") + }) + + test("does not use browser port for local dev", () => { + const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { + host: "localhost:3000", + protocol: "http:", + }) + expect(url.toString()).toBe("ws://localhost:4096/pty/pty_1/connect?directory=%2Frepo") }) test("uses secure websocket on https", () => { - const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { - host: "opencode.local", - protocol: "https:", + const url = ptySocketUrl("https://opencode.local", "pty_1", "/repo", { + host: "localhost:3000", + protocol: "http:", }) expect(url.toString()).toBe("wss://opencode.local/pty/pty_1/connect?directory=%2Frepo") }) - test("preserves browser port", () => { - const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { - host: "opencode.local:8443", - protocol: "https:", + test("preserves base url port", () => { + const url = ptySocketUrl("https://opencode.local:8443", "pty_1", "/repo", { + host: "localhost:3000", + protocol: "http:", }) expect(url.toString()).toBe("wss://opencode.local:8443/pty/pty_1/connect?directory=%2Frepo") }) diff --git a/packages/app/src/components/terminal-url.ts b/packages/app/src/components/terminal-url.ts index c2c9fb53c5..5f28e0e318 100644 --- a/packages/app/src/components/terminal-url.ts +++ b/packages/app/src/components/terminal-url.ts @@ -1,10 +1,12 @@ export function ptySocketUrl(base: string, id: string, directory: string, origin: { host: string; protocol: string }) { const root = `${origin.protocol}//${origin.host}` - const current = new URL(root) - const prefix = /^https?:\/\//.test(base) ? base : new URL(base || "/", root).toString() - const url = new URL(prefix.replace(/\/+$/, "") + `/pty/${id}/connect?directory=${encodeURIComponent(directory)}`) - url.hostname = current.hostname - url.port = current.port - url.protocol = origin.protocol === "https:" ? "wss:" : "ws:" + const absolute = /^https?:\/\//.test(base) + const resolved = absolute ? new URL(base) : new URL(base || "/", root) + + const url = new URL(resolved) + url.pathname = resolved.pathname.replace(/\/+$/, "") + `/pty/${id}/connect` + url.search = "" + url.searchParams.set("directory", directory) + url.protocol = resolved.protocol === "https:" ? "wss:" : "ws:" return url } From 31e2feb347e32adf173ab239b4ecb98d5127679a Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Wed, 4 Feb 2026 19:28:22 -0500 Subject: [PATCH 06/59] fix(tui): add hover states to question tool tabs (#12203) --- .../cli/cmd/tui/routes/session/question.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx index 88e99c6ea8..1565a30081 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx @@ -1,5 +1,5 @@ import { createStore } from "solid-js/store" -import { createMemo, For, Show } from "solid-js" +import { createMemo, createSignal, For, Show } from "solid-js" import { useKeyboard } from "@opentui/solid" import type { TextareaRenderable } from "@opentui/core" import { useKeybind } from "../../context/keybind" @@ -19,6 +19,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) { const questions = createMemo(() => props.request.questions) const single = createMemo(() => questions().length === 1 && questions()[0]?.multiple !== true) const tabs = createMemo(() => (single() ? 1 : questions().length + 1)) // questions + confirm tab (no confirm for single select) + const [tabHover, setTabHover] = createSignal(null) const [store, setStore] = createStore({ tab: 0, answers: [] as QuestionAnswer[], @@ -269,7 +270,15 @@ export function QuestionPrompt(props: { request: QuestionRequest }) { setTabHover(index())} + onMouseOut={() => setTabHover(null)} onMouseUp={() => selectTab(index())} > setTabHover("confirm")} + onMouseOut={() => setTabHover(null)} onMouseUp={() => selectTab(questions().length)} > Confirm From 4387f9fb9a6a364e88df51208ec40f21a948f7bd Mon Sep 17 00:00:00 2001 From: Ariane Emory <97994360+ariane-emory@users.noreply.github.com> Date: Wed, 4 Feb 2026 19:29:46 -0500 Subject: [PATCH 07/59] feat: Allow the function to hide or show thinking blocks to be bound to a key (resolves #12168) (#12171) --- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 1 + packages/opencode/src/config/config.ts | 1 + packages/sdk/js/src/v2/gen/types.gen.ts | 4 ++++ packages/web/src/content/docs/keybinds.mdx | 3 ++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 9a000f953c..8a38d9e6f1 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -546,6 +546,7 @@ export function Session() { { title: showThinking() ? "Hide thinking" : "Show thinking", value: "session.toggle.thinking", + keybind: "display_thinking", category: "Session", slash: { name: "thinking", diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 436e829839..7c7f1cc43e 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -899,6 +899,7 @@ export namespace Config { terminal_suspend: z.string().optional().default("ctrl+z").describe("Suspend terminal"), terminal_title_toggle: z.string().optional().default("none").describe("Toggle terminal title"), tips_toggle: z.string().optional().default("h").describe("Toggle tips on home screen"), + display_thinking: z.string().optional().default("none").describe("Toggle thinking blocks visibility"), }) .strict() .meta({ diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 8050b47d61..cb1606e3f6 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1309,6 +1309,10 @@ export type KeybindsConfig = { * Toggle tips on home screen */ tips_toggle?: string + /** + * Toggle thinking blocks visibility + */ + display_thinking?: string } /** diff --git a/packages/web/src/content/docs/keybinds.mdx b/packages/web/src/content/docs/keybinds.mdx index 51508a4f86..25fe2a1d91 100644 --- a/packages/web/src/content/docs/keybinds.mdx +++ b/packages/web/src/content/docs/keybinds.mdx @@ -97,7 +97,8 @@ OpenCode has a list of keybinds that you can customize through the OpenCode conf "history_next": "down", "terminal_suspend": "ctrl+z", "terminal_title_toggle": "none", - "tips_toggle": "h" + "tips_toggle": "h", + "display_thinking": "none" } } ``` From 2614342f979f067ddbd0e33e6884c8f8b92effdc Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 5 Feb 2026 00:30:42 +0000 Subject: [PATCH 08/59] chore: generate --- packages/sdk/openapi.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 3c70324649..1425a6e9c7 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -8883,6 +8883,11 @@ "description": "Toggle tips on home screen", "default": "h", "type": "string" + }, + "display_thinking": { + "description": "Toggle thinking blocks visibility", + "default": "none", + "type": "string" } }, "additionalProperties": false From 4086a9ae8ec0fa32ee05b369e1f956564acaa4c6 Mon Sep 17 00:00:00 2001 From: Filip <34747899+neriousy@users.noreply.github.com> Date: Thu, 5 Feb 2026 02:33:34 +0100 Subject: [PATCH 09/59] fix(app): refresh workspace sessions on project switch (#12189) --- packages/app/src/pages/layout.tsx | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 4ee54d3065..903b9eaa1f 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -2488,17 +2488,22 @@ export default function Layout(props: ParentProps) { } const LocalWorkspace = (props: { project: LocalProject; mobile?: boolean }): JSX.Element => { - const [workspaceStore, setWorkspaceStore] = globalSync.child(props.project.worktree) + const workspace = createMemo(() => { + const [store, setStore] = globalSync.child(props.project.worktree) + return { store, setStore } + }) const slug = createMemo(() => base64Encode(props.project.worktree)) - const sessions = createMemo(() => - workspaceStore.session - .filter((session) => session.directory === workspaceStore.path.directory) + const sessions = createMemo(() => { + const store = workspace().store + return store.session + .filter((session) => session.directory === store.path.directory) .filter((session) => !session.parentID && !session.time?.archived) - .toSorted(sortSessions(Date.now())), - ) + .toSorted(sortSessions(Date.now())) + }) const children = createMemo(() => { + const store = workspace().store const map = new Map() - for (const session of workspaceStore.session) { + for (const session of store.session) { if (!session.parentID) continue const existing = map.get(session.parentID) if (existing) { @@ -2509,11 +2514,11 @@ export default function Layout(props: ParentProps) { } return map }) - const booted = createMemo((prev) => prev || workspaceStore.status === "complete", false) + const booted = createMemo((prev) => prev || workspace().store.status === "complete", false) const loading = createMemo(() => !booted() && sessions().length === 0) - const hasMore = createMemo(() => workspaceStore.sessionTotal > sessions().length) + const hasMore = createMemo(() => workspace().store.sessionTotal > sessions().length) const loadMore = async () => { - setWorkspaceStore("limit", (limit) => limit + 5) + workspace().setStore("limit", (limit) => limit + 5) await globalSync.project.loadSessions(props.project.worktree) } From 173804c097da7b4190944cd0d024e833a8c413a1 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 4 Feb 2026 20:56:15 -0500 Subject: [PATCH 10/59] zen: set session affinity header --- .../app/src/routes/zen/util/provider/openai-compatible.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts b/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts index 699243d085..ce97a34d9b 100644 --- a/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts +++ b/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts @@ -26,6 +26,7 @@ export const oaCompatHelper: ProviderHelper = () => ({ modifyUrl: (providerApi: string) => providerApi + "/chat/completions", modifyHeaders: (headers: Headers, body: Record, apiKey: string) => { headers.set("authorization", `Bearer ${apiKey}`) + headers.set("x-session-affinity", headers.get("x-opencode-session") ?? "") }, modifyBody: (body: Record) => { return { From 843bbc973a86229fe75a021032b816890342d500 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 4 Feb 2026 20:56:34 -0500 Subject: [PATCH 11/59] wip: zen --- packages/console/core/package.json | 6 +++--- packages/console/core/script/update-models.ts | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/console/core/package.json b/packages/console/core/package.json index b6f2a8e900..3f69947016 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -25,9 +25,9 @@ "db": "sst shell drizzle-kit", "db-dev": "sst shell --stage dev -- drizzle-kit", "db-prod": "sst shell --stage production -- drizzle-kit", - "shell": "sst shell -- bun", - "shell-dev": "sst shell --stage dev -- bun", - "shell-prod": "sst shell --stage production -- bun", + "shell": "sst shell", + "shell-dev": "sst shell --stage dev", + "shell-prod": "sst shell --stage production", "update-models": "script/update-models.ts", "promote-models-to-dev": "script/promote-models.ts dev", "promote-models-to-prod": "script/promote-models.ts production", diff --git a/packages/console/core/script/update-models.ts b/packages/console/core/script/update-models.ts index d37c6a9aab..095c4aba85 100755 --- a/packages/console/core/script/update-models.ts +++ b/packages/console/core/script/update-models.ts @@ -17,10 +17,8 @@ const oldValues = Array.from({ length: PARTS }, (_, i) => { ?.split("=") .slice(1) .join("=") - // TODO - //if (!value) throw new Error(`ZEN_MODELS${i + 1} not found`) - //return value - return value ?? "" + if (!value) throw new Error(`ZEN_MODELS${i + 1} not found`) + return value }) // store the prettified json to a temp file From 556adad67bcdec0d29b878705175e9ebca544574 Mon Sep 17 00:00:00 2001 From: Dax Date: Wed, 4 Feb 2026 22:25:43 -0500 Subject: [PATCH 12/59] fix: wait for dependencies before loading custom tools and plugins (#12227) --- packages/opencode/src/config/config.ts | 19 ++++++-- packages/opencode/src/plugin/index.ts | 1 + packages/opencode/src/tool/registry.ts | 21 ++++----- packages/opencode/test/tool/registry.test.ts | 46 ++++++++++++++++++++ 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 7c7f1cc43e..dfb86dbe26 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -30,6 +30,7 @@ import { GlobalBus } from "@/bus/global" import { Event } from "../server/event" import { PackageRegistry } from "@/bun/registry" import { proxied } from "@/util/proxied" +import { iife } from "@/util/iife" export namespace Config { const log = Log.create({ service: "config" }) @@ -144,6 +145,8 @@ export namespace Config { log.debug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR }) } + const deps = [] + for (const dir of unique(directories)) { if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) { for (const file of ["opencode.jsonc", "opencode.json"]) { @@ -156,10 +159,12 @@ export namespace Config { } } - const shouldInstall = await needsInstall(dir) - if (shouldInstall) { - await installDependencies(dir) - } + deps.push( + iife(async () => { + const shouldInstall = await needsInstall(dir) + if (shouldInstall) await installDependencies(dir) + }), + ) result.command = mergeDeep(result.command ?? {}, await loadCommand(dir)) result.agent = mergeDeep(result.agent, await loadAgent(dir)) @@ -233,9 +238,15 @@ export namespace Config { return { config: result, directories, + deps, } }) + export async function waitForDependencies() { + const deps = await state().then((x) => x.deps) + await Promise.all(deps) + } + export async function installDependencies(dir: string) { const pkg = path.join(dir, "package.json") const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 6032935f84..4e5776e51d 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -44,6 +44,7 @@ export namespace Plugin { } const plugins = [...(config.plugin ?? [])] + if (plugins.length) await Config.waitForDependencies() if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) { plugins.push(...BUILTIN) } diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 3cb7715947..5ed5a879b4 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -35,18 +35,15 @@ export namespace ToolRegistry { const custom = [] as Tool.Info[] const glob = new Bun.Glob("{tool,tools}/*.{js,ts}") - for (const dir of await Config.directories()) { - for await (const match of glob.scan({ - cwd: dir, - absolute: true, - followSymlinks: true, - dot: true, - })) { - const namespace = path.basename(match, path.extname(match)) - const mod = await import(match) - for (const [id, def] of Object.entries(mod)) { - custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def)) - } + const matches = await Config.directories().then((dirs) => + dirs.flatMap((dir) => [...glob.scanSync({ cwd: dir, absolute: true, followSymlinks: true, dot: true })]), + ) + if (matches.length) await Config.waitForDependencies() + for (const match of matches) { + const namespace = path.basename(match, path.extname(match)) + const mod = await import(match) + for (const [id, def] of Object.entries(mod)) { + custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def)) } } diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index aea8b7088f..706a9e12ca 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -73,4 +73,50 @@ describe("tool.registry", () => { }, }) }) + + test("loads tools with external dependencies without crashing", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + const opencodeDir = path.join(dir, ".opencode") + await fs.mkdir(opencodeDir, { recursive: true }) + + const toolsDir = path.join(opencodeDir, "tools") + await fs.mkdir(toolsDir, { recursive: true }) + + await Bun.write( + path.join(opencodeDir, "package.json"), + JSON.stringify({ + name: "custom-tools", + dependencies: { + "@opencode-ai/plugin": "^0.0.0", + cowsay: "^1.6.0", + }, + }), + ) + + await Bun.write( + path.join(toolsDir, "cowsay.ts"), + [ + "import { say } from 'cowsay'", + "export default {", + " description: 'tool that imports cowsay at top level',", + " args: { text: { type: 'string' } },", + " execute: async ({ text }: { text: string }) => {", + " return say({ text })", + " },", + "}", + "", + ].join("\n"), + ) + }, + }) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const ids = await ToolRegistry.ids() + expect(ids).toContain("cowsay") + }, + }) + }) }) From 64e2bf8bf00eca1c6ad4486e29eb84f7907d1d35 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:04:03 -0600 Subject: [PATCH 13/59] tweak: adjust task tool description/input to alleviate tool call failures that sometimes occured w/ gpt models (#12214) --- packages/opencode/src/tool/task.ts | 19 +++++++++++++++---- packages/opencode/src/tool/task.txt | 6 +++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index ac28d9f322..6a34983f83 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -16,7 +16,12 @@ const parameters = z.object({ description: z.string().describe("A short (3-5 words) description of the task"), prompt: z.string().describe("The task for the agent to perform"), subagent_type: z.string().describe("The type of specialized agent to use for this task"), - session_id: z.string().describe("Existing Task session to continue").optional(), + task_id: z + .string() + .describe( + "This should only be set if you mean to resume a previous task (you can pass a prior task_id and the task will continue the same subagent session as before instead of creating a fresh one)", + ) + .optional(), command: z.string().describe("The command that triggered this task").optional(), }) @@ -60,8 +65,8 @@ export const TaskTool = Tool.define("task", async (ctx) => { const hasTaskPermission = agent.permission.some((rule) => rule.permission === "task") const session = await iife(async () => { - if (params.session_id) { - const found = await Session.get(params.session_id).catch(() => {}) + if (params.task_id) { + const found = await Session.get(params.task_id).catch(() => {}) if (found) return found } @@ -176,7 +181,13 @@ export const TaskTool = Tool.define("task", async (ctx) => { })) const text = result.parts.findLast((x) => x.type === "text")?.text ?? "" - const output = text + "\n\n" + ["", `session_id: ${session.id}`, ""].join("\n") + const output = [ + `task_id: ${session.id} (for resuming to continue this task if needed)`, + "", + "", + text, + "", + ].join("\n") return { title: params.description, diff --git a/packages/opencode/src/tool/task.txt b/packages/opencode/src/tool/task.txt index 7af2a6f60d..585cce8f9d 100644 --- a/packages/opencode/src/tool/task.txt +++ b/packages/opencode/src/tool/task.txt @@ -17,10 +17,10 @@ When NOT to use the Task tool: Usage notes: 1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses -2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result. -3. Each agent invocation is stateless unless you provide a session_id. Your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you. +2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result. The output includes a task_id you can reuse later to continue the same subagent session. +3. Each agent invocation starts with a fresh context unless you provide task_id to resume the same subagent session (which continues with its previous messages and tool outputs). When starting fresh, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you. 4. The agent's outputs should generally be trusted -5. Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent +5. Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent. Tell it how to verify its work if possible (e.g., relevant test commands). 6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement. Example usage (NOTE: The agents below are fictional examples for illustration only - use the actual agents listed above): From 9ef319f25fee3dbefae9fcbe27ef4c69ceca1293 Mon Sep 17 00:00:00 2001 From: "Tommy D. Rossi" Date: Thu, 5 Feb 2026 07:08:08 +0100 Subject: [PATCH 14/59] feat: add models.dev schema ref for model autocomplete in opencode.json (#12112) --- packages/opencode/src/config/config.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index dfb86dbe26..a4760239cc 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -35,6 +35,8 @@ import { iife } from "@/util/iife" export namespace Config { const log = Log.create({ service: "config" }) + const ModelRef = { $ref: "https://models.dev/model-schema.json#/$defs/Model" } + // Managed settings directory for enterprise deployments (highest priority, admin-controlled) // These settings override all user and project settings function getManagedConfigDir(): string { @@ -660,7 +662,7 @@ export namespace Config { template: z.string(), description: z.string().optional(), agent: z.string().optional(), - model: z.string().optional(), + model: z.string().optional().meta(ModelRef), subtask: z.boolean().optional(), }) export type Command = z.infer @@ -672,7 +674,7 @@ export namespace Config { export const Agent = z .object({ - model: z.string().optional(), + model: z.string().optional().meta(ModelRef), variant: z .string() .optional() @@ -1043,11 +1045,16 @@ export namespace Config { .array(z.string()) .optional() .describe("When set, ONLY these providers will be enabled. All other providers will be ignored"), - model: z.string().describe("Model to use in the format of provider/model, eg anthropic/claude-2").optional(), + model: z + .string() + .describe("Model to use in the format of provider/model, eg anthropic/claude-2") + .optional() + .meta(ModelRef), small_model: z .string() .describe("Small model to use for tasks like title generation in the format of provider/model") - .optional(), + .optional() + .meta(ModelRef), default_agent: z .string() .optional() From d3a247bfff57dca2737b4c82f8b3c4c998b06d17 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 5 Feb 2026 06:08:54 +0000 Subject: [PATCH 15/59] chore: generate --- packages/sdk/js/src/v2/gen/types.gen.ts | 15 +++++---------- packages/sdk/openapi.json | 10 ++++------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index cb1606e3f6..10483d71ed 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1379,7 +1379,7 @@ export type PermissionConfig = | PermissionActionConfig export type AgentConfig = { - model?: string + model?: Model /** * Default model variant for this agent (applies only when using the agent's configured model). */ @@ -1421,6 +1421,7 @@ export type AgentConfig = { permission?: PermissionConfig [key: string]: | unknown + | Model | string | number | { @@ -1650,7 +1651,7 @@ export type Config = { template: string description?: string agent?: string - model?: string + model?: Model subtask?: boolean } } @@ -1688,14 +1689,8 @@ export type Config = { * When set, ONLY these providers will be enabled. All other providers will be ignored */ enabled_providers?: Array - /** - * Model to use in the format of provider/model, eg anthropic/claude-2 - */ - model?: string - /** - * Small model to use for tasks like title generation in the format of provider/model - */ - small_model?: string + model?: Model + small_model?: Model /** * Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid. */ diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 1425a6e9c7..1033c1f138 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -9028,7 +9028,7 @@ "type": "object", "properties": { "model": { - "type": "string" + "$ref": "#/components/schemas/Model" }, "variant": { "description": "Default model variant for this agent (applies only when using the agent's configured model).", @@ -9528,7 +9528,7 @@ "type": "string" }, "model": { - "type": "string" + "$ref": "#/components/schemas/Model" }, "subtask": { "type": "boolean" @@ -9606,12 +9606,10 @@ } }, "model": { - "description": "Model to use in the format of provider/model, eg anthropic/claude-2", - "type": "string" + "$ref": "#/components/schemas/Model" }, "small_model": { - "description": "Small model to use for tasks like title generation in the format of provider/model", - "type": "string" + "$ref": "#/components/schemas/Model" }, "default_agent": { "description": "Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.", From 8c8d8881400db46abf723293e80f03309064834f Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:31:08 -0600 Subject: [PATCH 16/59] =?UTF-8?q?Revert:=20feat:=20add=20models.dev=20sche?= =?UTF-8?q?ma=20ref=20for=20model=20autocomplete=20in=20ope=E2=80=A6=20(#1?= =?UTF-8?q?2242)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/opencode/src/config/config.ts | 15 ++++----------- packages/sdk/js/src/v2/gen/types.gen.ts | 15 ++++++++++----- packages/sdk/openapi.json | 10 ++++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index a4760239cc..dfb86dbe26 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -35,8 +35,6 @@ import { iife } from "@/util/iife" export namespace Config { const log = Log.create({ service: "config" }) - const ModelRef = { $ref: "https://models.dev/model-schema.json#/$defs/Model" } - // Managed settings directory for enterprise deployments (highest priority, admin-controlled) // These settings override all user and project settings function getManagedConfigDir(): string { @@ -662,7 +660,7 @@ export namespace Config { template: z.string(), description: z.string().optional(), agent: z.string().optional(), - model: z.string().optional().meta(ModelRef), + model: z.string().optional(), subtask: z.boolean().optional(), }) export type Command = z.infer @@ -674,7 +672,7 @@ export namespace Config { export const Agent = z .object({ - model: z.string().optional().meta(ModelRef), + model: z.string().optional(), variant: z .string() .optional() @@ -1045,16 +1043,11 @@ export namespace Config { .array(z.string()) .optional() .describe("When set, ONLY these providers will be enabled. All other providers will be ignored"), - model: z - .string() - .describe("Model to use in the format of provider/model, eg anthropic/claude-2") - .optional() - .meta(ModelRef), + model: z.string().describe("Model to use in the format of provider/model, eg anthropic/claude-2").optional(), small_model: z .string() .describe("Small model to use for tasks like title generation in the format of provider/model") - .optional() - .meta(ModelRef), + .optional(), default_agent: z .string() .optional() diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 10483d71ed..cb1606e3f6 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1379,7 +1379,7 @@ export type PermissionConfig = | PermissionActionConfig export type AgentConfig = { - model?: Model + model?: string /** * Default model variant for this agent (applies only when using the agent's configured model). */ @@ -1421,7 +1421,6 @@ export type AgentConfig = { permission?: PermissionConfig [key: string]: | unknown - | Model | string | number | { @@ -1651,7 +1650,7 @@ export type Config = { template: string description?: string agent?: string - model?: Model + model?: string subtask?: boolean } } @@ -1689,8 +1688,14 @@ export type Config = { * When set, ONLY these providers will be enabled. All other providers will be ignored */ enabled_providers?: Array - model?: Model - small_model?: Model + /** + * Model to use in the format of provider/model, eg anthropic/claude-2 + */ + model?: string + /** + * Small model to use for tasks like title generation in the format of provider/model + */ + small_model?: string /** * Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid. */ diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 1033c1f138..1425a6e9c7 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -9028,7 +9028,7 @@ "type": "object", "properties": { "model": { - "$ref": "#/components/schemas/Model" + "type": "string" }, "variant": { "description": "Default model variant for this agent (applies only when using the agent's configured model).", @@ -9528,7 +9528,7 @@ "type": "string" }, "model": { - "$ref": "#/components/schemas/Model" + "type": "string" }, "subtask": { "type": "boolean" @@ -9606,10 +9606,12 @@ } }, "model": { - "$ref": "#/components/schemas/Model" + "description": "Model to use in the format of provider/model, eg anthropic/claude-2", + "type": "string" }, "small_model": { - "$ref": "#/components/schemas/Model" + "description": "Small model to use for tasks like title generation in the format of provider/model", + "type": "string" }, "default_agent": { "description": "Default agent to use when none is specified. Must be a primary agent. Falls back to 'build' if not set or if the specified agent is invalid.", From bec1148192792d438285e30b2473f41eea1af6a0 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 5 Feb 2026 01:50:05 -0600 Subject: [PATCH 17/59] fix: downgrade xai ai-sdk package due to errors (#12251) --- bun.lock | 8 +++++--- packages/opencode/package.json | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bun.lock b/bun.lock index 9b0d0eccc6..91b9fa3399 100644 --- a/bun.lock +++ b/bun.lock @@ -284,7 +284,7 @@ "@ai-sdk/provider-utils": "3.0.20", "@ai-sdk/togetherai": "1.0.34", "@ai-sdk/vercel": "1.0.33", - "@ai-sdk/xai": "2.0.56", + "@ai-sdk/xai": "2.0.51", "@clack/prompts": "1.0.0-alpha.1", "@gitlab/gitlab-ai-provider": "3.4.0", "@hono/standard-validator": "0.1.5", @@ -604,7 +604,7 @@ "@ai-sdk/vercel": ["@ai-sdk/vercel@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Qwjm+HdwKasu7L9bDUryBMGKDMscIEzMUkjw/33uGdJpktzyNW13YaNIObOZ2HkskqDMIQJSd4Ao2BBT8fEYLw=="], - "@ai-sdk/xai": ["@ai-sdk/xai@2.0.56", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FGlqwWc3tAYqDHE8r8hQGQLcMiPUwgz90oU2QygUH930OWtCLapFkSu114DgVaIN/qoM1DUX+inv0Ee74Fgp5g=="], + "@ai-sdk/xai": ["@ai-sdk/xai@2.0.51", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.30", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-AI3le03qiegkZvn9hpnpDwez49lOvQLj4QUBT8H41SMbrdTYOxn3ktTwrsSu90cNDdzKGMvoH0u2GHju1EdnCg=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -4016,7 +4016,7 @@ "@ai-sdk/vercel/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="], - "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="], + "@ai-sdk/xai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.30", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-thubwhRtv9uicAxSWwNpinM7hiL/0CkhL/ymPaHuKvI494J7HIzn8KQZQ2ymRz284WTIZnI7VMyyejxW4RMM6w=="], "@astrojs/cloudflare/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], @@ -4294,6 +4294,8 @@ "ai-gateway-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.32", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YspqqyJPzHjqWrjt4y/Wgc2aJgCcQj5uIJgZpq2Ar/lH30cEVhgE+keePDbjKpetD9UwNggCj7u6kO3unS23OQ=="], + "ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@2.0.56", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.32", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FGlqwWc3tAYqDHE8r8hQGQLcMiPUwgz90oU2QygUH930OWtCLapFkSu114DgVaIN/qoM1DUX+inv0Ee74Fgp5g=="], + "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index e03ad80093..a0d6892f9d 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -69,7 +69,7 @@ "@ai-sdk/provider-utils": "3.0.20", "@ai-sdk/togetherai": "1.0.34", "@ai-sdk/vercel": "1.0.33", - "@ai-sdk/xai": "2.0.56", + "@ai-sdk/xai": "2.0.51", "@clack/prompts": "1.0.0-alpha.1", "@gitlab/gitlab-ai-provider": "3.4.0", "@hono/standard-validator": "0.1.5", From 72de9fe7a6993e053d302b4f11f59ae3149e5a96 Mon Sep 17 00:00:00 2001 From: Zhiming Guo Date: Thu, 5 Feb 2026 18:54:07 +1100 Subject: [PATCH 18/59] fix(opencode): Fixes image reading with OpenAI-compatible providers like Kimi K2.5. (#11323) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Aiden Cline --- packages/opencode/src/session/message-v2.ts | 56 ++++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 6358c6c5e9..b6043b0325 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -438,6 +438,26 @@ export namespace MessageV2 { export function toModelMessages(input: WithParts[], model: Provider.Model): ModelMessage[] { const result: UIMessage[] = [] const toolNames = new Set() + // Track media from tool results that need to be injected as user messages + // for providers that don't support media in tool results. + // + // OpenAI-compatible APIs only support string content in tool results, so we need + // to extract media and inject as user messages. Other SDKs (anthropic, google, + // bedrock) handle type: "content" with media parts natively. + // + // Only apply this workaround if the model actually supports image input - + // otherwise there's no point extracting images. + const supportsMediaInToolResults = (() => { + if (model.api.npm === "@ai-sdk/anthropic") return true + if (model.api.npm === "@ai-sdk/openai") return true + if (model.api.npm === "@ai-sdk/amazon-bedrock") return true + if (model.api.npm === "@ai-sdk/google-vertex/anthropic") return true + if (model.api.npm === "@ai-sdk/google") { + const id = model.api.id.toLowerCase() + return id.includes("gemini-3") && !id.includes("gemini-2") + } + return false + })() const toModelOutput = (output: unknown) => { if (typeof output === "string") { @@ -514,6 +534,7 @@ export namespace MessageV2 { if (msg.info.role === "assistant") { const differentModel = `${model.providerID}/${model.id}` !== `${msg.info.providerID}/${msg.info.modelID}` + const media: Array<{ mime: string; url: string }> = [] if ( msg.info.error && @@ -545,11 +566,23 @@ export namespace MessageV2 { if (part.state.status === "completed") { const outputText = part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output const attachments = part.state.time.compacted ? [] : (part.state.attachments ?? []) + + // For providers that don't support media in tool results, extract media files + // (images, PDFs) to be sent as a separate user message + const isMediaAttachment = (a: { mime: string }) => + a.mime.startsWith("image/") || a.mime === "application/pdf" + const mediaAttachments = attachments.filter(isMediaAttachment) + const nonMediaAttachments = attachments.filter((a) => !isMediaAttachment(a)) + if (!supportsMediaInToolResults && mediaAttachments.length > 0) { + media.push(...mediaAttachments) + } + const finalAttachments = supportsMediaInToolResults ? attachments : nonMediaAttachments + const output = - attachments.length > 0 + finalAttachments.length > 0 ? { text: outputText, - attachments, + attachments: finalAttachments, } : outputText @@ -593,6 +626,25 @@ export namespace MessageV2 { } if (assistantMessage.parts.length > 0) { result.push(assistantMessage) + // Inject pending media as a user message for providers that don't support + // media (images, PDFs) in tool results + if (media.length > 0) { + result.push({ + id: Identifier.ascending("message"), + role: "user", + parts: [ + { + type: "text" as const, + text: "Attached image(s) from tool result:", + }, + ...media.map((attachment) => ({ + type: "file" as const, + url: attachment.url, + mediaType: attachment.mime, + })), + ], + }) + } } } } From 195731f347bcf1d5266935e6a587d598ae029662 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 5 Feb 2026 08:00:44 +0000 Subject: [PATCH 19/59] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index fc964a8788..9ba6b762c6 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-9XlAYCNdBhw8NmfJoYNjvQYhSn02rFhWvbJtlOnnCjc=", - "aarch64-linux": "sha256-Mdz3gAy8auN7mhMHRaWyH/exHGO9eYDyUMQKqscg6Xc=", - "aarch64-darwin": "sha256-NDB6+NVZ4+9+Yds/cjEGQAn9Tl/LRuEjEH6wV5dTdVg=", - "x86_64-darwin": "sha256-LGJ5TJYgyK8Vn0BliEeJdoblcubj5ZIjvJoUtdVXfvU=" + "x86_64-linux": "sha256-OJ4a65oeJpSXfmL2tNslGhgvo1WPNBLL/5pWlDOK0jY=", + "aarch64-linux": "sha256-DoJcXOKb/SNN78Fp4z3sQSlzPLaUQ0Ss/jZa2tL6hdc=", + "aarch64-darwin": "sha256-jtMqFH7uYzgI1ICASAePJOj1QeaQfEWuoME7gyrvinA=", + "x86_64-darwin": "sha256-doY5oQKtOlmF4aegKvchU1M86zDjGNfMwE+GvfpZa0g=" } } From fa20bc296b134d17840e993656e175e11ee15b3d Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 5 Feb 2026 16:42:05 +0800 Subject: [PATCH 20/59] app: remove extra x padding around prompt input on mobile --- packages/app/src/pages/session.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 3fa71244bf..7af99d8936 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -2630,7 +2630,7 @@ export default function Page() { {/* Prompt input */}
(promptDock = el)} - class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 px-4 md:px-0 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none" + class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none" >
Date: Thu, 5 Feb 2026 06:04:28 -0600 Subject: [PATCH 21/59] chore: cleanup --- packages/desktop/src/index.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index 9ef680ed86..b54e1f79f2 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -30,17 +30,6 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) { void initI18n() -// Floating UI can call getComputedStyle with non-elements (e.g., null refs, virtual elements). -// This happens on all platforms (WebView2 on Windows, WKWebView on macOS), not just Windows. -const originalGetComputedStyle = window.getComputedStyle -window.getComputedStyle = ((elt: Element, pseudoElt?: string | null) => { - if (!(elt instanceof Element)) { - // Fall back to a safe element when a non-element is passed. - return originalGetComputedStyle(document.documentElement, pseudoElt ?? undefined) - } - return originalGetComputedStyle(elt, pseudoElt ?? undefined) -}) as typeof window.getComputedStyle - let update: Update | null = null const deepLinkEvent = "opencode:deep-link" From bf7af99a3f07787bdf6781af0dfc57bcc169fe3b Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 5 Feb 2026 06:20:19 -0600 Subject: [PATCH 22/59] fix(app): terminal URL issues --- .../app/src/components/terminal-url.test.ts | 44 ------------------- packages/app/src/components/terminal-url.ts | 12 ----- packages/app/src/components/terminal.tsx | 4 +- 3 files changed, 2 insertions(+), 58 deletions(-) delete mode 100644 packages/app/src/components/terminal-url.test.ts delete mode 100644 packages/app/src/components/terminal-url.ts diff --git a/packages/app/src/components/terminal-url.test.ts b/packages/app/src/components/terminal-url.test.ts deleted file mode 100644 index 8548d70876..0000000000 --- a/packages/app/src/components/terminal-url.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { ptySocketUrl } from "./terminal-url" - -describe("ptySocketUrl", () => { - test("uses sdk host for absolute server url", () => { - const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { - host: "192.168.1.50:4096", - protocol: "http:", - }) - expect(url.toString()).toBe("ws://localhost:4096/pty/pty_1/connect?directory=%2Frepo") - }) - - test("does not use browser port for local dev", () => { - const url = ptySocketUrl("http://localhost:4096", "pty_1", "/repo", { - host: "localhost:3000", - protocol: "http:", - }) - expect(url.toString()).toBe("ws://localhost:4096/pty/pty_1/connect?directory=%2Frepo") - }) - - test("uses secure websocket on https", () => { - const url = ptySocketUrl("https://opencode.local", "pty_1", "/repo", { - host: "localhost:3000", - protocol: "http:", - }) - expect(url.toString()).toBe("wss://opencode.local/pty/pty_1/connect?directory=%2Frepo") - }) - - test("preserves base url port", () => { - const url = ptySocketUrl("https://opencode.local:8443", "pty_1", "/repo", { - host: "localhost:3000", - protocol: "http:", - }) - expect(url.toString()).toBe("wss://opencode.local:8443/pty/pty_1/connect?directory=%2Frepo") - }) - - test("handles slash base url", () => { - const url = ptySocketUrl("/", "pty_1", "/repo", { - host: "192.168.1.50:4096", - protocol: "http:", - }) - expect(url.toString()).toBe("ws://192.168.1.50:4096/pty/pty_1/connect?directory=%2Frepo") - }) -}) diff --git a/packages/app/src/components/terminal-url.ts b/packages/app/src/components/terminal-url.ts deleted file mode 100644 index 5f28e0e318..0000000000 --- a/packages/app/src/components/terminal-url.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function ptySocketUrl(base: string, id: string, directory: string, origin: { host: string; protocol: string }) { - const root = `${origin.protocol}//${origin.host}` - const absolute = /^https?:\/\//.test(base) - const resolved = absolute ? new URL(base) : new URL(base || "/", root) - - const url = new URL(resolved) - url.pathname = resolved.pathname.replace(/\/+$/, "") + `/pty/${id}/connect` - url.search = "" - url.searchParams.set("directory", directory) - url.protocol = resolved.protocol === "https:" ? "wss:" : "ws:" - return url -} diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index a6ce8acfa0..e87670f59b 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -8,7 +8,6 @@ import { LocalPTY } from "@/context/terminal" import { resolveThemeVariant, useTheme, withAlpha, type HexColor } from "@opencode-ai/ui/theme" import { useLanguage } from "@/context/language" import { showToast } from "@opencode-ai/ui/toast" -import { ptySocketUrl } from "./terminal-url" export interface TerminalProps extends ComponentProps<"div"> { pty: LocalPTY @@ -164,7 +163,8 @@ export const Terminal = (props: TerminalProps) => { const once = { value: false } - const url = ptySocketUrl(sdk.url, local.pty.id, sdk.directory, window.location) + const url = new URL(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`) + url.protocol = url.protocol === "https:" ? "wss:" : "ws:" if (window.__OPENCODE__?.serverPassword) { url.username = "opencode" url.password = window.__OPENCODE__?.serverPassword From ef09dddaa5ea7a533fa2042068761cd4a9cbcb35 Mon Sep 17 00:00:00 2001 From: Edin <86423329+edoedac0@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:58:14 +0100 Subject: [PATCH 23/59] feat(i18n): add Bosnian locale (#12283) --- packages/app/src/context/language.tsx | 8 + packages/app/src/i18n/bs.ts | 747 ++++++++++++++++++++++++++ packages/app/src/i18n/en.ts | 1 + packages/desktop/src/i18n/bs.ts | 32 ++ packages/desktop/src/i18n/index.ts | 39 +- packages/ui/src/i18n/bs.ts | 108 ++++ 6 files changed, 933 insertions(+), 2 deletions(-) create mode 100644 packages/app/src/i18n/bs.ts create mode 100644 packages/desktop/src/i18n/bs.ts create mode 100644 packages/ui/src/i18n/bs.ts diff --git a/packages/app/src/context/language.tsx b/packages/app/src/context/language.tsx index 1b93c9b051..bf081996b0 100644 --- a/packages/app/src/context/language.tsx +++ b/packages/app/src/context/language.tsx @@ -18,6 +18,7 @@ import { dict as ar } from "@/i18n/ar" import { dict as no } from "@/i18n/no" import { dict as br } from "@/i18n/br" import { dict as th } from "@/i18n/th" +import { dict as bs } from "@/i18n/bs" import { dict as uiEn } from "@opencode-ai/ui/i18n/en" import { dict as uiZh } from "@opencode-ai/ui/i18n/zh" import { dict as uiZht } from "@opencode-ai/ui/i18n/zht" @@ -33,6 +34,7 @@ import { dict as uiAr } from "@opencode-ai/ui/i18n/ar" import { dict as uiNo } from "@opencode-ai/ui/i18n/no" import { dict as uiBr } from "@opencode-ai/ui/i18n/br" import { dict as uiTh } from "@opencode-ai/ui/i18n/th" +import { dict as uiBs } from "@opencode-ai/ui/i18n/bs" export type Locale = | "en" @@ -50,6 +52,7 @@ export type Locale = | "no" | "br" | "th" + | "bs" type RawDictionary = typeof en & typeof uiEn type Dictionary = i18n.Flatten @@ -66,6 +69,7 @@ const LOCALES: readonly Locale[] = [ "ja", "pl", "ru", + "bs", "ar", "no", "br", @@ -99,6 +103,7 @@ function detectLocale(): Locale { return "no" if (language.toLowerCase().startsWith("pt")) return "br" if (language.toLowerCase().startsWith("th")) return "th" + if (language.toLowerCase().startsWith("bs")) return "bs" } return "en" @@ -129,6 +134,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont if (store.locale === "no") return "no" if (store.locale === "br") return "br" if (store.locale === "th") return "th" + if (store.locale === "bs") return "bs" return "en" }) @@ -154,6 +160,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont if (locale() === "no") return { ...base, ...i18n.flatten({ ...no, ...uiNo }) } if (locale() === "br") return { ...base, ...i18n.flatten({ ...br, ...uiBr }) } if (locale() === "th") return { ...base, ...i18n.flatten({ ...th, ...uiTh }) } + if (locale() === "bs") return { ...base, ...i18n.flatten({ ...bs, ...uiBs }) } return { ...base, ...i18n.flatten({ ...ko, ...uiKo }) } }) @@ -175,6 +182,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont no: "language.no", br: "language.br", th: "language.th", + bs: "language.bs", } const label = (value: Locale) => t(labelKey[value]) diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts new file mode 100644 index 0000000000..ce37989c25 --- /dev/null +++ b/packages/app/src/i18n/bs.ts @@ -0,0 +1,747 @@ +export const dict = { + "command.category.suggested": "Predloženo", + "command.category.view": "Prikaz", + "command.category.project": "Projekat", + "command.category.provider": "Provajder", + "command.category.server": "Server", + "command.category.session": "Sesija", + "command.category.theme": "Tema", + "command.category.language": "Jezik", + "command.category.file": "Datoteka", + "command.category.context": "Kontekst", + "command.category.terminal": "Terminal", + "command.category.model": "Model", + "command.category.mcp": "MCP", + "command.category.agent": "Agent", + "command.category.permissions": "Dozvole", + "command.category.workspace": "Radni prostor", + "command.category.settings": "Postavke", + + "theme.scheme.system": "Sistem", + "theme.scheme.light": "Svijetlo", + "theme.scheme.dark": "Tamno", + + "command.sidebar.toggle": "Prikaži/sakrij bočnu traku", + "command.project.open": "Otvori projekat", + "command.provider.connect": "Poveži provajdera", + "command.server.switch": "Promijeni server", + "command.settings.open": "Otvori postavke", + "command.session.previous": "Prethodna sesija", + "command.session.next": "Sljedeća sesija", + "command.session.previous.unseen": "Prethodna nepročitana sesija", + "command.session.next.unseen": "Sljedeća nepročitana sesija", + "command.session.archive": "Arhiviraj sesiju", + + "command.palette": "Paleta komandi", + + "command.theme.cycle": "Promijeni temu", + "command.theme.set": "Koristi temu: {{theme}}", + "command.theme.scheme.cycle": "Promijeni šemu boja", + "command.theme.scheme.set": "Koristi šemu boja: {{scheme}}", + + "command.language.cycle": "Promijeni jezik", + "command.language.set": "Koristi jezik: {{language}}", + + "command.session.new": "Nova sesija", + "command.file.open": "Otvori datoteku", + "command.tab.close": "Zatvori karticu", + "command.context.addSelection": "Dodaj odabir u kontekst", + "command.context.addSelection.description": "Dodaj odabrane linije iz trenutne datoteke", + "command.terminal.toggle": "Prikaži/sakrij terminal", + "command.fileTree.toggle": "Prikaži/sakrij stablo datoteka", + "command.review.toggle": "Prikaži/sakrij pregled", + "command.terminal.new": "Novi terminal", + "command.terminal.new.description": "Kreiraj novu karticu terminala", + "command.steps.toggle": "Prikaži/sakrij korake", + "command.steps.toggle.description": "Prikaži ili sakrij korake za trenutnu poruku", + "command.message.previous": "Prethodna poruka", + "command.message.previous.description": "Idi na prethodnu korisničku poruku", + "command.message.next": "Sljedeća poruka", + "command.message.next.description": "Idi na sljedeću korisničku poruku", + "command.model.choose": "Odaberi model", + "command.model.choose.description": "Odaberi drugi model", + "command.mcp.toggle": "Prikaži/sakrij MCP-ove", + "command.mcp.toggle.description": "Prikaži/sakrij MCP-ove", + "command.agent.cycle": "Promijeni agenta", + "command.agent.cycle.description": "Prebaci na sljedećeg agenta", + "command.agent.cycle.reverse": "Promijeni agenta unazad", + "command.agent.cycle.reverse.description": "Prebaci na prethodnog agenta", + "command.model.variant.cycle": "Promijeni nivo razmišljanja", + "command.model.variant.cycle.description": "Prebaci na sljedeći nivo", + "command.permissions.autoaccept.enable": "Automatski prihvataj izmjene", + "command.permissions.autoaccept.disable": "Zaustavi automatsko prihvatanje izmjena", + "command.workspace.toggle": "Prikaži/sakrij radne prostore", + "command.workspace.toggle.description": "Omogući ili onemogući više radnih prostora u bočnoj traci", + "command.session.undo": "Poništi", + "command.session.undo.description": "Poništi posljednju poruku", + "command.session.redo": "Vrati", + "command.session.redo.description": "Vrati posljednju poništenu poruku", + "command.session.compact": "Sažmi sesiju", + "command.session.compact.description": "Sažmi sesiju kako bi se smanjio kontekst", + "command.session.fork": "Fork iz poruke", + "command.session.fork.description": "Kreiraj novu sesiju iz prethodne poruke", + "command.session.share": "Podijeli sesiju", + "command.session.share.description": "Podijeli ovu sesiju i kopiraj URL u međuspremnik", + "command.session.unshare": "Ukini dijeljenje sesije", + "command.session.unshare.description": "Zaustavi dijeljenje ove sesije", + + "palette.search.placeholder": "Pretraži datoteke, komande i sesije", + "palette.empty": "Nema rezultata", + "palette.group.commands": "Komande", + "palette.group.files": "Datoteke", + + "dialog.provider.search.placeholder": "Pretraži provajdere", + "dialog.provider.empty": "Nema pronađenih provajdera", + "dialog.provider.group.popular": "Popularno", + "dialog.provider.group.other": "Ostalo", + "dialog.provider.tag.recommended": "Preporučeno", + "dialog.provider.opencode.note": "Kurirani modeli uključujući Claude, GPT, Gemini i druge", + "dialog.provider.anthropic.note": "Direktan pristup Claude modelima, uključujući Pro i Max", + "dialog.provider.copilot.note": "Claude modeli za pomoć pri kodiranju", + "dialog.provider.openai.note": "GPT modeli za brze, sposobne opšte AI zadatke", + "dialog.provider.google.note": "Gemini modeli za brze, strukturirane odgovore", + "dialog.provider.openrouter.note": "Pristup svim podržanim modelima preko jednog provajdera", + "dialog.provider.vercel.note": "Jedinstven pristup AI modelima uz pametno rutiranje", + + "dialog.model.select.title": "Odaberi model", + "dialog.model.search.placeholder": "Pretraži modele", + "dialog.model.empty": "Nema rezultata za modele", + "dialog.model.manage": "Upravljaj modelima", + "dialog.model.manage.description": "Prilagodi koji se modeli prikazuju u izborniku modela.", + + "dialog.model.unpaid.freeModels.title": "Besplatni modeli koje obezbjeđuje OpenCode", + "dialog.model.unpaid.addMore.title": "Dodaj još modela od popularnih provajdera", + + "dialog.provider.viewAll": "Prikaži više provajdera", + + "provider.connect.title": "Poveži {{provider}}", + "provider.connect.title.anthropicProMax": "Prijavi se putem Claude Pro/Max", + "provider.connect.selectMethod": "Odaberi način prijave za {{provider}}.", + "provider.connect.method.apiKey": "API ključ", + "provider.connect.status.inProgress": "Autorizacija je u toku...", + "provider.connect.status.waiting": "Čekanje na autorizaciju...", + "provider.connect.status.failed": "Autorizacija nije uspjela: {{error}}", + "provider.connect.apiKey.description": + "Unesi svoj {{provider}} API ključ da povežeš račun i koristiš {{provider}} modele u OpenCode-u.", + "provider.connect.apiKey.label": "{{provider}} API ključ", + "provider.connect.apiKey.placeholder": "API ključ", + "provider.connect.apiKey.required": "API ključ je obavezan", + "provider.connect.opencodeZen.line1": + "OpenCode Zen ti daje pristup kuriranom skupu pouzdanih, optimizovanih modela za coding agente.", + "provider.connect.opencodeZen.line2": + "Sa jednim API ključem dobijaš pristup modelima kao što su Claude, GPT, Gemini, GLM i drugi.", + "provider.connect.opencodeZen.visit.prefix": "Posjeti ", + "provider.connect.opencodeZen.visit.link": "opencode.ai/zen", + "provider.connect.opencodeZen.visit.suffix": " da preuzmeš svoj API ključ.", + "provider.connect.oauth.code.visit.prefix": "Posjeti ", + "provider.connect.oauth.code.visit.link": "ovaj link", + "provider.connect.oauth.code.visit.suffix": + " da preuzmeš autorizacijski kod i povežeš račun te koristiš {{provider}} modele u OpenCode-u.", + "provider.connect.oauth.code.label": "{{method}} autorizacijski kod", + "provider.connect.oauth.code.placeholder": "Autorizacijski kod", + "provider.connect.oauth.code.required": "Autorizacijski kod je obavezan", + "provider.connect.oauth.code.invalid": "Nevažeći autorizacijski kod", + "provider.connect.oauth.auto.visit.prefix": "Posjeti ", + "provider.connect.oauth.auto.visit.link": "ovaj link", + "provider.connect.oauth.auto.visit.suffix": + " i unesi kod ispod da povežeš račun i koristiš {{provider}} modele u OpenCode-u.", + "provider.connect.oauth.auto.confirmationCode": "Kod za potvrdu", + "provider.connect.toast.connected.title": "{{provider}} povezan", + "provider.connect.toast.connected.description": "{{provider}} modeli su sada dostupni za korištenje.", + + "provider.disconnect.toast.disconnected.title": "{{provider}} odspojen", + "provider.disconnect.toast.disconnected.description": "{{provider}} modeli više nisu dostupni.", + + "model.tag.free": "Besplatno", + "model.tag.latest": "Najnovije", + "model.provider.anthropic": "Anthropic", + "model.provider.openai": "OpenAI", + "model.provider.google": "Google", + "model.provider.xai": "xAI", + "model.provider.meta": "Meta", + "model.input.text": "tekst", + "model.input.image": "slika", + "model.input.audio": "zvuk", + "model.input.video": "video", + "model.input.pdf": "pdf", + "model.tooltip.allows": "Podržava: {{inputs}}", + "model.tooltip.reasoning.allowed": "Podržava rasuđivanje", + "model.tooltip.reasoning.none": "Bez rasuđivanja", + "model.tooltip.context": "Limit konteksta {{limit}}", + + "common.search.placeholder": "Pretraži", + "common.goBack": "Nazad", + "common.goForward": "Naprijed", + "common.loading": "Učitavanje", + "common.loading.ellipsis": "...", + "common.cancel": "Otkaži", + "common.connect": "Poveži", + "common.disconnect": "Prekini vezu", + "common.submit": "Pošalji", + "common.save": "Sačuvaj", + "common.saving": "Čuvanje...", + "common.default": "Podrazumijevano", + "common.attachment": "prilog", + + "prompt.placeholder.shell": "Unesi shell naredbu...", + "prompt.placeholder.normal": 'Pitaj bilo šta... "{{example}}"', + "prompt.placeholder.summarizeComments": "Sažmi komentare…", + "prompt.placeholder.summarizeComment": "Sažmi komentar…", + "prompt.mode.shell": "Shell", + "prompt.mode.shell.exit": "esc za izlaz", + + "prompt.example.1": "Popravi TODO u bazi koda", + "prompt.example.2": "Koji je tehnološki stack ovog projekta?", + "prompt.example.3": "Popravi pokvarene testove", + "prompt.example.4": "Objasni kako radi autentifikacija", + "prompt.example.5": "Pronađi i popravi sigurnosne ranjivosti", + "prompt.example.6": "Dodaj jedinične testove za servis korisnika", + "prompt.example.7": "Refaktoriši ovu funkciju da bude čitljivija", + "prompt.example.8": "Šta znači ova greška?", + "prompt.example.9": "Pomozi mi da otklonim ovu grešku", + "prompt.example.10": "Generiši API dokumentaciju", + "prompt.example.11": "Optimizuj upite prema bazi podataka", + "prompt.example.12": "Dodaj validaciju ulaza", + "prompt.example.13": "Napravi novu komponentu za...", + "prompt.example.14": "Kako da deployam ovaj projekat?", + "prompt.example.15": "Pregledaj moj kod prema najboljim praksama", + "prompt.example.16": "Dodaj obradu grešaka u ovu funkciju", + "prompt.example.17": "Objasni ovaj regex obrazac", + "prompt.example.18": "Pretvori ovo u TypeScript", + "prompt.example.19": "Dodaj logovanje kroz cijelu bazu koda", + "prompt.example.20": "Koje su zavisnosti zastarjele?", + "prompt.example.21": "Pomozi mi da napišem migracijsku skriptu", + "prompt.example.22": "Implementiraj keširanje za ovaj endpoint", + "prompt.example.23": "Dodaj paginaciju u ovu listu", + "prompt.example.24": "Napravi CLI komandu za...", + "prompt.example.25": "Kako ovdje rade varijable okruženja?", + + "prompt.popover.emptyResults": "Nema rezultata", + "prompt.popover.emptyCommands": "Nema komandi", + "prompt.dropzone.label": "Spusti slike ili PDF-ove ovdje", + "prompt.slash.badge.custom": "prilagođeno", + "prompt.slash.badge.skill": "skill", + "prompt.slash.badge.mcp": "mcp", + "prompt.context.active": "aktivno", + "prompt.context.includeActiveFile": "Uključi aktivnu datoteku", + "prompt.context.removeActiveFile": "Ukloni aktivnu datoteku iz konteksta", + "prompt.context.removeFile": "Ukloni datoteku iz konteksta", + "prompt.action.attachFile": "Priloži datoteku", + "prompt.attachment.remove": "Ukloni prilog", + "prompt.action.send": "Pošalji", + "prompt.action.stop": "Zaustavi", + + "prompt.toast.pasteUnsupported.title": "Nepodržano lijepljenje", + "prompt.toast.pasteUnsupported.description": "Ovdje se mogu zalijepiti samo slike ili PDF-ovi.", + "prompt.toast.modelAgentRequired.title": "Odaberi agenta i model", + "prompt.toast.modelAgentRequired.description": "Odaberi agenta i model prije slanja upita.", + "prompt.toast.worktreeCreateFailed.title": "Neuspješno kreiranje worktree-a", + "prompt.toast.sessionCreateFailed.title": "Neuspješno kreiranje sesije", + "prompt.toast.shellSendFailed.title": "Neuspješno slanje shell naredbe", + "prompt.toast.commandSendFailed.title": "Neuspješno slanje komande", + "prompt.toast.promptSendFailed.title": "Neuspješno slanje upita", + + "dialog.mcp.title": "MCP-ovi", + "dialog.mcp.description": "{{enabled}} od {{total}} omogućeno", + "dialog.mcp.empty": "Nema konfigurisnih MCP-ova", + + "dialog.lsp.empty": "LSP-ovi se automatski otkrivaju prema tipu datoteke", + "dialog.plugins.empty": "Plugini su konfigurisani u opencode.json", + + "mcp.status.connected": "povezano", + "mcp.status.failed": "neuspjelo", + "mcp.status.needs_auth": "potrebna autentifikacija", + "mcp.status.disabled": "onemogućeno", + + "dialog.fork.empty": "Nema poruka za fork", + + "dialog.directory.search.placeholder": "Pretraži foldere", + "dialog.directory.empty": "Nema pronađenih foldera", + + "dialog.server.title": "Serveri", + "dialog.server.description": "Promijeni na koji se OpenCode server ova aplikacija povezuje.", + "dialog.server.search.placeholder": "Pretraži servere", + "dialog.server.empty": "Još nema servera", + "dialog.server.add.title": "Dodaj server", + "dialog.server.add.url": "URL servera", + "dialog.server.add.placeholder": "http://localhost:4096", + "dialog.server.add.error": "Nije moguće povezati se na server", + "dialog.server.add.checking": "Provjera...", + "dialog.server.add.button": "Dodaj server", + "dialog.server.default.title": "Podrazumijevani server", + "dialog.server.default.description": + "Poveži se na ovaj server pri pokretanju aplikacije umjesto pokretanja lokalnog servera. Potreban je restart.", + "dialog.server.default.none": "Nije odabran server", + "dialog.server.default.set": "Postavi trenutni server kao podrazumijevani", + "dialog.server.default.clear": "Očisti", + "dialog.server.action.remove": "Ukloni server", + + "dialog.server.menu.edit": "Uredi", + "dialog.server.menu.default": "Postavi kao podrazumijevano", + "dialog.server.menu.defaultRemove": "Ukloni podrazumijevano", + "dialog.server.menu.delete": "Izbriši", + "dialog.server.current": "Trenutni server", + "dialog.server.status.default": "Podrazumijevano", + + "dialog.project.edit.title": "Uredi projekat", + "dialog.project.edit.name": "Naziv", + "dialog.project.edit.icon": "Ikonica", + "dialog.project.edit.icon.alt": "Ikonica projekta", + "dialog.project.edit.icon.hint": "Klikni ili prevuci sliku", + "dialog.project.edit.icon.recommended": "Preporučeno: 128x128px", + "dialog.project.edit.color": "Boja", + "dialog.project.edit.color.select": "Odaberi boju {{color}}", + "dialog.project.edit.worktree.startup": "Skripta za pokretanje radnog prostora", + "dialog.project.edit.worktree.startup.description": "Pokreće se nakon kreiranja novog radnog prostora (worktree).", + "dialog.project.edit.worktree.startup.placeholder": "npr. bun install", + + "context.breakdown.title": "Razlaganje konteksta", + "context.breakdown.note": + 'Približna raspodjela ulaznih tokena. "Ostalo" uključuje definicije alata i dodatni overhead.', + "context.breakdown.system": "Sistem", + "context.breakdown.user": "Korisnik", + "context.breakdown.assistant": "Asistent", + "context.breakdown.tool": "Pozivi alata", + "context.breakdown.other": "Ostalo", + + "context.systemPrompt.title": "Sistemski prompt", + "context.rawMessages.title": "Sirove poruke", + + "context.stats.session": "Sesija", + "context.stats.messages": "Poruke", + "context.stats.provider": "Provajder", + "context.stats.model": "Model", + "context.stats.limit": "Limit konteksta", + "context.stats.totalTokens": "Ukupno tokena", + "context.stats.usage": "Korištenje", + "context.stats.inputTokens": "Ulazni tokeni", + "context.stats.outputTokens": "Izlazni tokeni", + "context.stats.reasoningTokens": "Tokeni za rasuđivanje", + "context.stats.cacheTokens": "Cache tokeni (čitanje/pisanje)", + "context.stats.userMessages": "Korisničke poruke", + "context.stats.assistantMessages": "Poruke asistenta", + "context.stats.totalCost": "Ukupni trošak", + "context.stats.sessionCreated": "Sesija kreirana", + "context.stats.lastActivity": "Posljednja aktivnost", + + "context.usage.tokens": "Tokeni", + "context.usage.usage": "Korištenje", + "context.usage.cost": "Trošak", + "context.usage.clickToView": "Klikni da vidiš kontekst", + "context.usage.view": "Prikaži korištenje konteksta", + + "language.en": "English", + "language.zh": "简体中文", + "language.zht": "繁體中文", + "language.ko": "한국어", + "language.de": "Deutsch", + "language.es": "Español", + "language.fr": "Français", + "language.da": "Dansk", + "language.ja": "日本語", + "language.pl": "Polski", + "language.ru": "Русский", + "language.ar": "العربية", + "language.no": "Norsk", + "language.br": "Português (Brasil)", + "language.bs": "Bosanski", + "language.th": "ไทย", + + "toast.language.title": "Jezik", + "toast.language.description": "Prebačeno na {{language}}", + + "toast.theme.title": "Tema promijenjena", + "toast.scheme.title": "Šema boja", + + "toast.workspace.enabled.title": "Radni prostori omogućeni", + "toast.workspace.enabled.description": "Više worktree-ova se sada prikazuje u bočnoj traci", + "toast.workspace.disabled.title": "Radni prostori onemogućeni", + "toast.workspace.disabled.description": "Samo glavni worktree se prikazuje u bočnoj traci", + + "toast.permissions.autoaccept.on.title": "Automatsko prihvatanje izmjena", + "toast.permissions.autoaccept.on.description": "Dozvole za izmjene i pisanje biće automatski odobrene", + "toast.permissions.autoaccept.off.title": "Zaustavljeno automatsko prihvatanje izmjena", + "toast.permissions.autoaccept.off.description": "Dozvole za izmjene i pisanje zahtijevaće odobrenje", + + "toast.model.none.title": "Nije odabran model", + "toast.model.none.description": "Poveži provajdera da sažmeš ovu sesiju", + + "toast.file.loadFailed.title": "Neuspjelo učitavanje datoteke", + "toast.file.listFailed.title": "Neuspješno listanje datoteka", + + "toast.context.noLineSelection.title": "Nema odabranih linija", + "toast.context.noLineSelection.description": "Prvo odaberi raspon linija u kartici datoteke.", + + "toast.session.share.copyFailed.title": "Neuspjelo kopiranje URL-a u međuspremnik", + "toast.session.share.success.title": "Sesija podijeljena", + "toast.session.share.success.description": "URL za dijeljenje je kopiran u međuspremnik!", + "toast.session.share.failed.title": "Neuspjelo dijeljenje sesije", + "toast.session.share.failed.description": "Došlo je do greške prilikom dijeljenja sesije", + + "toast.session.unshare.success.title": "Dijeljenje sesije ukinuto", + "toast.session.unshare.success.description": "Dijeljenje sesije je uspješno ukinuto!", + "toast.session.unshare.failed.title": "Neuspjelo ukidanje dijeljenja", + "toast.session.unshare.failed.description": "Došlo je do greške prilikom ukidanja dijeljenja", + + "toast.session.listFailed.title": "Neuspjelo učitavanje sesija za {{project}}", + + "toast.update.title": "Dostupno ažuriranje", + "toast.update.description": "Nova verzija OpenCode-a ({{version}}) je dostupna za instalaciju.", + "toast.update.action.installRestart": "Instaliraj i restartuj", + "toast.update.action.notYet": "Ne još", + + "error.page.title": "Nešto je pošlo po zlu", + "error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.", + "error.page.details.label": "Detalji greške", + "error.page.action.restart": "Restartuj", + "error.page.action.checking": "Provjera...", + "error.page.action.checkUpdates": "Provjeri ažuriranja", + "error.page.action.updateTo": "Ažuriraj na {{version}}", + "error.page.report.prefix": "Molimo prijavi ovu grešku OpenCode timu", + "error.page.report.discord": "na Discordu", + "error.page.version": "Verzija: {{version}}", + + "error.dev.rootNotFound": + "Korijenski element nije pronađen. Da li si zaboravio da ga dodaš u index.html? Ili je možda id atribut pogrešno napisan?", + + "error.globalSync.connectFailed": "Nije moguće povezati se na server. Da li server radi na `{{url}}`?", + + "error.chain.unknown": "Nepoznata greška", + "error.chain.causedBy": "Uzrok:", + "error.chain.apiError": "API greška", + "error.chain.status": "Status: {{status}}", + "error.chain.retryable": "Može se ponoviti: {{retryable}}", + "error.chain.responseBody": "Tijelo odgovora:\n{{body}}", + "error.chain.didYouMean": "Da li si mislio: {{suggestions}}", + "error.chain.modelNotFound": "Model nije pronađen: {{provider}}/{{model}}", + "error.chain.checkConfig": "Provjeri konfiguraciju (opencode.json) - nazive provajdera/modela", + "error.chain.mcpFailed": 'MCP server "{{name}}" nije uspio. Napomena: OpenCode još ne podržava MCP autentifikaciju.', + "error.chain.providerAuthFailed": "Autentifikacija provajdera nije uspjela ({{provider}}): {{message}}", + "error.chain.providerInitFailed": + 'Neuspjelo inicijalizovanje provajdera "{{provider}}". Provjeri kredencijale i konfiguraciju.', + "error.chain.configJsonInvalid": "Konfiguracijska datoteka na {{path}} nije važeći JSON(C)", + "error.chain.configJsonInvalidWithMessage": "Konfiguracijska datoteka na {{path}} nije važeći JSON(C): {{message}}", + "error.chain.configDirectoryTypo": + 'Direktorij "{{dir}}" u {{path}} nije ispravan. Preimenuj direktorij u "{{suggestion}}" ili ga ukloni. Ovo je česta greška u kucanju.', + "error.chain.configFrontmatterError": "Neuspjelo parsiranje frontmatter-a u {{path}}:\n{{message}}", + "error.chain.configInvalid": "Konfiguracijska datoteka na {{path}} nije ispravna", + "error.chain.configInvalidWithMessage": "Konfiguracijska datoteka na {{path}} nije ispravna: {{message}}", + + "notification.permission.title": "Potrebna dozvola", + "notification.permission.description": "{{sessionTitle}} u {{projectName}} traži dozvolu", + "notification.question.title": "Pitanje", + "notification.question.description": "{{sessionTitle}} u {{projectName}} ima pitanje", + "notification.action.goToSession": "Idi na sesiju", + + "notification.session.responseReady.title": "Odgovor je spreman", + "notification.session.error.title": "Greška sesije", + "notification.session.error.fallbackDescription": "Došlo je do greške", + + "home.recentProjects": "Nedavni projekti", + "home.empty.title": "Nema nedavnih projekata", + "home.empty.description": "Kreni tako što ćeš otvoriti lokalni projekat", + + "session.tab.session": "Sesija", + "session.tab.review": "Pregled", + "session.tab.context": "Kontekst", + "session.panel.reviewAndFiles": "Pregled i datoteke", + "session.review.filesChanged": "Izmijenjeno {{count}} datoteka", + "session.review.change.one": "Izmjena", + "session.review.change.other": "Izmjene", + "session.review.loadingChanges": "Učitavanje izmjena...", + "session.review.empty": "Još nema izmjena u ovoj sesiji", + "session.review.noChanges": "Nema izmjena", + + "session.files.selectToOpen": "Odaberi datoteku za otvaranje", + "session.files.all": "Sve datoteke", + "session.files.binaryContent": "Binarna datoteka (sadržaj se ne može prikazati)", + + "session.messages.renderEarlier": "Prikaži ranije poruke", + "session.messages.loadingEarlier": "Učitavanje ranijih poruka...", + "session.messages.loadEarlier": "Učitaj ranije poruke", + "session.messages.loading": "Učitavanje poruka...", + "session.messages.jumpToLatest": "Idi na najnovije", + + "session.context.addToContext": "Dodaj {{selection}} u kontekst", + + "session.new.worktree.main": "Glavna grana", + "session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})", + "session.new.worktree.create": "Kreiraj novi worktree", + "session.new.lastModified": "Posljednja izmjena", + + "session.header.search.placeholder": "Pretraži {{project}}", + "session.header.searchFiles": "Pretraži datoteke", + + "status.popover.trigger": "Status", + "status.popover.ariaLabel": "Konfiguracije servera", + "status.popover.tab.servers": "Serveri", + "status.popover.tab.mcp": "MCP", + "status.popover.tab.lsp": "LSP", + "status.popover.tab.plugins": "Plugini", + "status.popover.action.manageServers": "Upravljaj serverima", + + "session.share.popover.title": "Objavi na webu", + "session.share.popover.description.shared": "Ova sesija je javna na webu. Dostupna je svima koji imaju link.", + "session.share.popover.description.unshared": "Podijeli sesiju javno na webu. Biće dostupna svima koji imaju link.", + "session.share.action.share": "Podijeli", + "session.share.action.publish": "Objavi", + "session.share.action.publishing": "Objavljivanje...", + "session.share.action.unpublish": "Poništi objavu", + "session.share.action.unpublishing": "Poništavanje objave...", + "session.share.action.view": "Prikaži", + "session.share.copy.copied": "Kopirano", + "session.share.copy.copyLink": "Kopiraj link", + + "lsp.tooltip.none": "Nema LSP servera", + "lsp.label.connected": "{{count}} LSP", + + "prompt.loading": "Učitavanje upita...", + "terminal.loading": "Učitavanje terminala...", + "terminal.title": "Terminal", + "terminal.title.numbered": "Terminal {{number}}", + "terminal.close": "Zatvori terminal", + "terminal.connectionLost.title": "Veza prekinuta", + "terminal.connectionLost.description": + "Veza s terminalom je prekinuta. Ovo se može desiti kada se server restartuje.", + + "common.closeTab": "Zatvori karticu", + "common.dismiss": "Odbaci", + "common.requestFailed": "Zahtjev nije uspio", + "common.moreOptions": "Više opcija", + "common.learnMore": "Saznaj više", + "common.rename": "Preimenuj", + "common.reset": "Resetuj", + "common.archive": "Arhiviraj", + "common.delete": "Izbriši", + "common.close": "Zatvori", + "common.edit": "Uredi", + "common.loadMore": "Učitaj još", + "common.key.esc": "ESC", + + "sidebar.menu.toggle": "Prikaži/sakrij meni", + "sidebar.nav.projectsAndSessions": "Projekti i sesije", + "sidebar.settings": "Postavke", + "sidebar.help": "Pomoć", + "sidebar.workspaces.enable": "Omogući radne prostore", + "sidebar.workspaces.disable": "Onemogući radne prostore", + "sidebar.gettingStarted.title": "Početak", + "sidebar.gettingStarted.line1": "OpenCode uključuje besplatne modele, tako da možeš odmah početi.", + "sidebar.gettingStarted.line2": "Poveži bilo kojeg provajdera da koristiš modele, npr. Claude, GPT, Gemini itd.", + "sidebar.project.recentSessions": "Nedavne sesije", + "sidebar.project.viewAllSessions": "Prikaži sve sesije", + + "app.name.desktop": "OpenCode Desktop", + + "settings.section.desktop": "Desktop", + "settings.section.server": "Server", + "settings.tab.general": "Opšte", + "settings.tab.shortcuts": "Prečice", + + "settings.general.section.appearance": "Izgled", + "settings.general.section.notifications": "Sistemske obavijesti", + "settings.general.section.updates": "Ažuriranja", + "settings.general.section.sounds": "Zvučni efekti", + + "settings.general.row.language.title": "Jezik", + "settings.general.row.language.description": "Promijeni jezik prikaza u OpenCode-u", + "settings.general.row.appearance.title": "Izgled", + "settings.general.row.appearance.description": "Prilagodi kako OpenCode izgleda na tvom uređaju", + "settings.general.row.theme.title": "Tema", + "settings.general.row.theme.description": "Prilagodi temu OpenCode-a.", + "settings.general.row.font.title": "Font", + "settings.general.row.font.description": "Prilagodi monospace font koji se koristi u blokovima koda", + + "settings.general.row.releaseNotes.title": "Bilješke o izdanju", + "settings.general.row.releaseNotes.description": 'Prikaži iskačuće prozore "Šta je novo" nakon ažuriranja', + + "settings.updates.row.startup.title": "Provjeri ažuriranja pri pokretanju", + "settings.updates.row.startup.description": "Automatski provjerava ažuriranja kada se OpenCode pokrene", + "settings.updates.row.check.title": "Provjeri ažuriranja", + "settings.updates.row.check.description": "Ručno provjeri ažuriranja i instaliraj ako su dostupna", + "settings.updates.action.checkNow": "Provjeri sada", + "settings.updates.action.checking": "Provjera...", + "settings.updates.toast.latest.title": "Sve je ažurno", + "settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.", + "font.option.ibmPlexMono": "IBM Plex Mono", + "font.option.cascadiaCode": "Cascadia Code", + "font.option.firaCode": "Fira Code", + "font.option.hack": "Hack", + "font.option.inconsolata": "Inconsolata", + "font.option.intelOneMono": "Intel One Mono", + "font.option.iosevka": "Iosevka", + "font.option.jetbrainsMono": "JetBrains Mono", + "font.option.mesloLgs": "Meslo LGS", + "font.option.robotoMono": "Roboto Mono", + "font.option.sourceCodePro": "Source Code Pro", + "font.option.ubuntuMono": "Ubuntu Mono", + "sound.option.alert01": "Upozorenje 01", + "sound.option.alert02": "Upozorenje 02", + "sound.option.alert03": "Upozorenje 03", + "sound.option.alert04": "Upozorenje 04", + "sound.option.alert05": "Upozorenje 05", + "sound.option.alert06": "Upozorenje 06", + "sound.option.alert07": "Upozorenje 07", + "sound.option.alert08": "Upozorenje 08", + "sound.option.alert09": "Upozorenje 09", + "sound.option.alert10": "Upozorenje 10", + "sound.option.bipbop01": "Bip-bop 01", + "sound.option.bipbop02": "Bip-bop 02", + "sound.option.bipbop03": "Bip-bop 03", + "sound.option.bipbop04": "Bip-bop 04", + "sound.option.bipbop05": "Bip-bop 05", + "sound.option.bipbop06": "Bip-bop 06", + "sound.option.bipbop07": "Bip-bop 07", + "sound.option.bipbop08": "Bip-bop 08", + "sound.option.bipbop09": "Bip-bop 09", + "sound.option.bipbop10": "Bip-bop 10", + "sound.option.staplebops01": "Staplebops 01", + "sound.option.staplebops02": "Staplebops 02", + "sound.option.staplebops03": "Staplebops 03", + "sound.option.staplebops04": "Staplebops 04", + "sound.option.staplebops05": "Staplebops 05", + "sound.option.staplebops06": "Staplebops 06", + "sound.option.staplebops07": "Staplebops 07", + "sound.option.nope01": "Ne 01", + "sound.option.nope02": "Ne 02", + "sound.option.nope03": "Ne 03", + "sound.option.nope04": "Ne 04", + "sound.option.nope05": "Ne 05", + "sound.option.nope06": "Ne 06", + "sound.option.nope07": "Ne 07", + "sound.option.nope08": "Ne 08", + "sound.option.nope09": "Ne 09", + "sound.option.nope10": "Ne 10", + "sound.option.nope11": "Ne 11", + "sound.option.nope12": "Ne 12", + "sound.option.yup01": "Da 01", + "sound.option.yup02": "Da 02", + "sound.option.yup03": "Da 03", + "sound.option.yup04": "Da 04", + "sound.option.yup05": "Da 05", + "sound.option.yup06": "Da 06", + + "settings.general.notifications.agent.title": "Agent", + "settings.general.notifications.agent.description": + "Prikaži sistemsku obavijest kada agent završi ili zahtijeva pažnju", + "settings.general.notifications.permissions.title": "Dozvole", + "settings.general.notifications.permissions.description": "Prikaži sistemsku obavijest kada je potrebna dozvola", + "settings.general.notifications.errors.title": "Greške", + "settings.general.notifications.errors.description": "Prikaži sistemsku obavijest kada dođe do greške", + + "settings.general.sounds.agent.title": "Agent", + "settings.general.sounds.agent.description": "Pusti zvuk kada agent završi ili zahtijeva pažnju", + "settings.general.sounds.permissions.title": "Dozvole", + "settings.general.sounds.permissions.description": "Pusti zvuk kada je potrebna dozvola", + "settings.general.sounds.errors.title": "Greške", + "settings.general.sounds.errors.description": "Pusti zvuk kada dođe do greške", + + "settings.shortcuts.title": "Prečice na tastaturi", + "settings.shortcuts.reset.button": "Vrati na podrazumijevano", + "settings.shortcuts.reset.toast.title": "Prečice resetovane", + "settings.shortcuts.reset.toast.description": "Prečice na tastaturi su vraćene na podrazumijevane.", + "settings.shortcuts.conflict.title": "Prečica je već u upotrebi", + "settings.shortcuts.conflict.description": "{{keybind}} je već dodijeljeno za {{titles}}.", + "settings.shortcuts.unassigned": "Nedodijeljeno", + "settings.shortcuts.pressKeys": "Pritisni tastere", + "settings.shortcuts.search.placeholder": "Pretraži prečice", + "settings.shortcuts.search.empty": "Nema pronađenih prečica", + + "settings.shortcuts.group.general": "Opšte", + "settings.shortcuts.group.session": "Sesija", + "settings.shortcuts.group.navigation": "Navigacija", + "settings.shortcuts.group.modelAndAgent": "Model i agent", + "settings.shortcuts.group.terminal": "Terminal", + "settings.shortcuts.group.prompt": "Upit", + + "settings.providers.title": "Provajderi", + "settings.providers.description": "Postavke provajdera će se ovdje moći podešavati.", + "settings.providers.section.connected": "Povezani provajderi", + "settings.providers.connected.empty": "Nema povezanih provajdera", + "settings.providers.section.popular": "Popularni provajderi", + "settings.providers.tag.environment": "Okruženje", + "settings.providers.tag.config": "Konfiguracija", + "settings.providers.tag.custom": "Prilagođeno", + "settings.providers.tag.other": "Ostalo", + "settings.models.title": "Modeli", + "settings.models.description": "Postavke modela će se ovdje moći podešavati.", + "settings.agents.title": "Agenti", + "settings.agents.description": "Postavke agenata će se ovdje moći podešavati.", + "settings.commands.title": "Komande", + "settings.commands.description": "Postavke komandi će se ovdje moći podešavati.", + "settings.mcp.title": "MCP", + "settings.mcp.description": "MCP postavke će se ovdje moći podešavati.", + + "settings.permissions.title": "Dozvole", + "settings.permissions.description": "Kontroliši koje alate server smije koristiti po defaultu.", + "settings.permissions.section.tools": "Alati", + "settings.permissions.toast.updateFailed.title": "Neuspjelo ažuriranje dozvola", + + "settings.permissions.action.allow": "Dozvoli", + "settings.permissions.action.ask": "Pitaj", + "settings.permissions.action.deny": "Zabrani", + + "settings.permissions.tool.read.title": "Čitanje", + "settings.permissions.tool.read.description": "Čitanje datoteke (podudara se s putanjom datoteke)", + "settings.permissions.tool.edit.title": "Uređivanje", + "settings.permissions.tool.edit.description": + "Mijenjanje datoteka, uključujući izmjene, pisanja, patch-eve i multi-izmjene", + "settings.permissions.tool.glob.title": "Glob", + "settings.permissions.tool.glob.description": "Podudaranje datoteka pomoću glob šablona", + "settings.permissions.tool.grep.title": "Grep", + "settings.permissions.tool.grep.description": "Pretraživanje sadržaja datoteka pomoću regularnih izraza", + "settings.permissions.tool.list.title": "Lista", + "settings.permissions.tool.list.description": "Listanje datoteka unutar direktorija", + "settings.permissions.tool.bash.title": "Bash", + "settings.permissions.tool.bash.description": "Pokretanje shell komandi", + "settings.permissions.tool.task.title": "Zadatak", + "settings.permissions.tool.task.description": "Pokretanje pod-agenta", + "settings.permissions.tool.skill.title": "Vještina", + "settings.permissions.tool.skill.description": "Učitaj vještinu po nazivu", + "settings.permissions.tool.lsp.title": "LSP", + "settings.permissions.tool.lsp.description": "Pokreni upite jezičnog servera", + "settings.permissions.tool.todoread.title": "Čitanje liste zadataka", + "settings.permissions.tool.todoread.description": "Čitanje liste zadataka", + "settings.permissions.tool.todowrite.title": "Ažuriranje liste zadataka", + "settings.permissions.tool.todowrite.description": "Ažuriraj listu zadataka", + "settings.permissions.tool.webfetch.title": "Web preuzimanje", + "settings.permissions.tool.webfetch.description": "Preuzmi sadržaj sa URL-a", + "settings.permissions.tool.websearch.title": "Web pretraga", + "settings.permissions.tool.websearch.description": "Pretražuj web", + "settings.permissions.tool.codesearch.title": "Pretraga koda", + "settings.permissions.tool.codesearch.description": "Pretraži kod na webu", + "settings.permissions.tool.external_directory.title": "Vanjski direktorij", + "settings.permissions.tool.external_directory.description": "Pristup datotekama izvan direktorija projekta", + "settings.permissions.tool.doom_loop.title": "Beskonačna petlja", + "settings.permissions.tool.doom_loop.description": "Otkriva ponovljene pozive alata sa identičnim unosom", + + "session.delete.failed.title": "Neuspjelo brisanje sesije", + "session.delete.title": "Izbriši sesiju", + "session.delete.confirm": 'Izbriši sesiju "{{name}}"?', + "session.delete.button": "Izbriši sesiju", + + "workspace.new": "Novi radni prostor", + "workspace.type.local": "lokalno", + "workspace.type.sandbox": "sandbox", + "workspace.create.failed.title": "Neuspješno kreiranje radnog prostora", + "workspace.delete.failed.title": "Neuspješno brisanje radnog prostora", + "workspace.resetting.title": "Resetovanje radnog prostora", + "workspace.resetting.description": "Ovo može potrajati minut.", + "workspace.reset.failed.title": "Neuspješno resetovanje radnog prostora", + "workspace.reset.success.title": "Radni prostor resetovan", + "workspace.reset.success.description": "Radni prostor sada odgovara podrazumijevanoj grani.", + "workspace.error.stillPreparing": "Radni prostor se još priprema", + "workspace.status.checking": "Provjera neobjedinjenih promjena...", + "workspace.status.error": "Nije moguće provjeriti git status.", + "workspace.status.clean": "Nisu pronađene neobjedinjene promjene.", + "workspace.status.dirty": "Pronađene su neobjedinjene promjene u ovom radnom prostoru.", + "workspace.delete.title": "Izbriši radni prostor", + "workspace.delete.confirm": 'Izbriši radni prostor "{{name}}"?', + "workspace.delete.button": "Izbriši radni prostor", + "workspace.reset.title": "Resetuj radni prostor", + "workspace.reset.confirm": 'Resetuj radni prostor "{{name}}"?', + "workspace.reset.button": "Resetuj radni prostor", + "workspace.reset.archived.none": "Nijedna aktivna sesija neće biti arhivirana.", + "workspace.reset.archived.one": "1 sesija će biti arhivirana.", + "workspace.reset.archived.many": "Biće arhivirano {{count}} sesija.", + "workspace.reset.note": "Ovo će resetovati radni prostor da odgovara podrazumijevanoj grani.", +} diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index fb71a578af..7c4d3a44af 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -343,6 +343,7 @@ export const dict = { "language.ar": "العربية", "language.no": "Norsk", "language.br": "Português (Brasil)", + "language.bs": "Bosanski", "language.th": "ไทย", "toast.language.title": "Language", diff --git a/packages/desktop/src/i18n/bs.ts b/packages/desktop/src/i18n/bs.ts new file mode 100644 index 0000000000..861253bb87 --- /dev/null +++ b/packages/desktop/src/i18n/bs.ts @@ -0,0 +1,32 @@ +export const dict = { + "desktop.menu.checkForUpdates": "Provjeri ažuriranja...", + "desktop.menu.installCli": "Instaliraj CLI...", + "desktop.menu.reloadWebview": "Ponovo učitavanje webview-a", + "desktop.menu.restart": "Restartuj", + + "desktop.dialog.chooseFolder": "Odaberi folder", + "desktop.dialog.chooseFile": "Odaberi datoteku", + "desktop.dialog.saveFile": "Sačuvaj datoteku", + + "desktop.updater.checkFailed.title": "Provjera ažuriranja nije uspjela", + "desktop.updater.checkFailed.message": "Nije moguće provjeriti ažuriranja", + "desktop.updater.none.title": "Nema dostupnog ažuriranja", + "desktop.updater.none.message": "Već koristiš najnoviju verziju OpenCode-a", + "desktop.updater.downloadFailed.title": "Ažuriranje nije uspjelo", + "desktop.updater.downloadFailed.message": "Neuspjelo preuzimanje ažuriranja", + "desktop.updater.downloaded.title": "Ažuriranje preuzeto", + "desktop.updater.downloaded.prompt": + "Verzija {{version}} OpenCode-a je preuzeta. Želiš li da je instaliraš i ponovo pokreneš aplikaciju?", + "desktop.updater.installFailed.title": "Ažuriranje nije uspjelo", + "desktop.updater.installFailed.message": "Neuspjela instalacija ažuriranja", + + "desktop.cli.installed.title": "CLI instaliran", + "desktop.cli.installed.message": + "CLI je instaliran u {{path}}\n\nRestartuj terminal da bi koristio komandu 'opencode'.", + "desktop.cli.failed.title": "Instalacija nije uspjela", + "desktop.cli.failed.message": "Neuspjela instalacija CLI-a: {{error}}", + + "desktop.error.serverStartFailed.title": "OpenCode se nije mogao pokrenuti", + "desktop.error.serverStartFailed.description": + "Lokalni OpenCode server se nije mogao pokrenuti. Restartuj aplikaciju ili provjeri mrežne postavke (VPN/proxy) i pokušaj ponovo.", +} diff --git a/packages/desktop/src/i18n/index.ts b/packages/desktop/src/i18n/index.ts index f2496346fc..376769e282 100644 --- a/packages/desktop/src/i18n/index.ts +++ b/packages/desktop/src/i18n/index.ts @@ -15,6 +15,7 @@ import { dict as desktopRu } from "./ru" import { dict as desktopAr } from "./ar" import { dict as desktopNo } from "./no" import { dict as desktopBr } from "./br" +import { dict as desktopBs } from "./bs" import { dict as appEn } from "../../../app/src/i18n/en" import { dict as appZh } from "../../../app/src/i18n/zh" @@ -30,13 +31,45 @@ import { dict as appRu } from "../../../app/src/i18n/ru" import { dict as appAr } from "../../../app/src/i18n/ar" import { dict as appNo } from "../../../app/src/i18n/no" import { dict as appBr } from "../../../app/src/i18n/br" +import { dict as appBs } from "../../../app/src/i18n/bs" -export type Locale = "en" | "zh" | "zht" | "ko" | "de" | "es" | "fr" | "da" | "ja" | "pl" | "ru" | "ar" | "no" | "br" +export type Locale = + | "en" + | "zh" + | "zht" + | "ko" + | "de" + | "es" + | "fr" + | "da" + | "ja" + | "pl" + | "ru" + | "ar" + | "no" + | "br" + | "bs" type RawDictionary = typeof appEn & typeof desktopEn type Dictionary = i18n.Flatten -const LOCALES: readonly Locale[] = ["en", "zh", "zht", "ko", "de", "es", "fr", "da", "ja", "pl", "ru", "ar", "no", "br"] +const LOCALES: readonly Locale[] = [ + "en", + "zh", + "zht", + "ko", + "de", + "es", + "fr", + "da", + "ja", + "pl", + "ru", + "bs", + "ar", + "no", + "br", +] function detectLocale(): Locale { if (typeof navigator !== "object") return "en" @@ -64,6 +97,7 @@ function detectLocale(): Locale { ) return "no" if (language.toLowerCase().startsWith("pt")) return "br" + if (language.toLowerCase().startsWith("bs")) return "bs" } return "en" @@ -108,6 +142,7 @@ function build(locale: Locale): Dictionary { if (locale === "ar") return { ...base, ...i18n.flatten(appAr), ...i18n.flatten(desktopAr) } if (locale === "no") return { ...base, ...i18n.flatten(appNo), ...i18n.flatten(desktopNo) } if (locale === "br") return { ...base, ...i18n.flatten(appBr), ...i18n.flatten(desktopBr) } + if (locale === "bs") return { ...base, ...i18n.flatten(appBs), ...i18n.flatten(desktopBs) } return { ...base, ...i18n.flatten(appKo), ...i18n.flatten(desktopKo) } } diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts new file mode 100644 index 0000000000..75f0783bc4 --- /dev/null +++ b/packages/ui/src/i18n/bs.ts @@ -0,0 +1,108 @@ +import { dict as en } from "./en" + +type Keys = keyof typeof en + +export const dict = { + "ui.sessionReview.title": "Promjene sesije", + "ui.sessionReview.title.lastTurn": "Promjene u posljednjem potezu", + "ui.sessionReview.diffStyle.unified": "Ujedinjeno", + "ui.sessionReview.diffStyle.split": "Podijeljeno", + "ui.sessionReview.expandAll": "Proširi sve", + "ui.sessionReview.collapseAll": "Sažmi sve", + "ui.sessionReview.change.added": "Dodano", + "ui.sessionReview.change.removed": "Uklonjeno", + + "ui.lineComment.label.prefix": "Komentar na ", + "ui.lineComment.label.suffix": "", + "ui.lineComment.editorLabel.prefix": "Komentarišeš ", + "ui.lineComment.editorLabel.suffix": "", + "ui.lineComment.placeholder": "Dodaj komentar", + "ui.lineComment.submit": "Komentariši", + + "ui.sessionTurn.steps.show": "Prikaži korake", + "ui.sessionTurn.steps.hide": "Sakrij korake", + "ui.sessionTurn.summary.response": "Odgovor", + "ui.sessionTurn.diff.showMore": "Prikaži još izmjena ({{count}})", + + "ui.sessionTurn.retry.retrying": "ponovni pokušaj", + "ui.sessionTurn.retry.inSeconds": "za {{seconds}}s", + + "ui.sessionTurn.status.delegating": "Delegiranje posla", + "ui.sessionTurn.status.planning": "Planiranje sljedećih koraka", + "ui.sessionTurn.status.gatheringContext": "Prikupljanje konteksta", + "ui.sessionTurn.status.searchingCodebase": "Pretraživanje baze koda", + "ui.sessionTurn.status.searchingWeb": "Pretraživanje weba", + "ui.sessionTurn.status.makingEdits": "Pravljenje izmjena", + "ui.sessionTurn.status.runningCommands": "Pokretanje komandi", + "ui.sessionTurn.status.thinking": "Razmišljanje", + "ui.sessionTurn.status.thinkingWithTopic": "Razmišljanje - {{topic}}", + "ui.sessionTurn.status.gatheringThoughts": "Sređivanje misli", + "ui.sessionTurn.status.consideringNextSteps": "Razmatranje sljedećih koraka", + + "ui.messagePart.diagnostic.error": "Greška", + "ui.messagePart.title.edit": "Uredi", + "ui.messagePart.title.write": "Napiši", + "ui.messagePart.option.typeOwnAnswer": "Unesi svoj odgovor", + "ui.messagePart.review.title": "Pregledaj svoje odgovore", + + "ui.list.loading": "Učitavanje", + "ui.list.empty": "Nema rezultata", + "ui.list.clearFilter": "Očisti filter", + "ui.list.emptyWithFilter.prefix": "Nema rezultata za", + "ui.list.emptyWithFilter.suffix": "", + + "ui.messageNav.newMessage": "Nova poruka", + + "ui.textField.copyToClipboard": "Kopiraj u međuspremnik", + "ui.textField.copyLink": "Kopiraj link", + "ui.textField.copied": "Kopirano", + + "ui.imagePreview.alt": "Pregled slike", + + "ui.tool.read": "Čitanje", + "ui.tool.loaded": "Učitano", + "ui.tool.list": "Listanje", + "ui.tool.glob": "Glob", + "ui.tool.grep": "Grep", + "ui.tool.webfetch": "Web preuzimanje", + "ui.tool.shell": "Shell", + "ui.tool.patch": "Patch", + "ui.tool.todos": "Lista zadataka", + "ui.tool.todos.read": "Čitanje liste zadataka", + "ui.tool.questions": "Pitanja", + "ui.tool.agent": "{{type}} agent", + + "ui.common.file.one": "datoteka", + "ui.common.file.other": "datoteke", + "ui.common.question.one": "pitanje", + "ui.common.question.other": "pitanja", + + "ui.common.add": "Dodaj", + "ui.common.cancel": "Otkaži", + "ui.common.confirm": "Potvrdi", + "ui.common.dismiss": "Odbaci", + "ui.common.close": "Zatvori", + "ui.common.next": "Dalje", + "ui.common.submit": "Pošalji", + + "ui.permission.deny": "Zabrani", + "ui.permission.allowAlways": "Uvijek dozvoli", + "ui.permission.allowOnce": "Dozvoli jednom", + + "ui.message.expand": "Proširi poruku", + "ui.message.collapse": "Sažmi poruku", + "ui.message.copy": "Kopiraj", + "ui.message.copied": "Kopirano!", + "ui.message.attachment.alt": "prilog", + + "ui.patch.action.deleted": "Obrisano", + "ui.patch.action.created": "Kreirano", + "ui.patch.action.moved": "Premješteno", + "ui.patch.action.patched": "Primijenjeno", + + "ui.question.subtitle.answered": "{{count}} odgovoreno", + "ui.question.answer.none": "(nema odgovora)", + "ui.question.review.notAnswered": "(nije odgovoreno)", + "ui.question.multiHint": "(odaberi sve što važi)", + "ui.question.custom.placeholder": "Unesi svoj odgovor...", +} satisfies Partial> From 05529f66d7bbf4b210c14c205dccf3a6942ddd0d Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 5 Feb 2026 07:02:31 -0600 Subject: [PATCH 24/59] fix(app): copy buttons smaller and out of the way --- packages/ui/src/components/icon-button.css | 5 +++++ packages/ui/src/components/icon-button.tsx | 2 +- packages/ui/src/components/markdown.tsx | 2 +- packages/ui/src/components/message-part.css | 2 +- packages/ui/src/components/message-part.tsx | 2 ++ packages/ui/src/components/session-turn.css | 18 ++++++++++++------ packages/ui/src/components/session-turn.tsx | 19 +++++++++++-------- 7 files changed, 33 insertions(+), 17 deletions(-) diff --git a/packages/ui/src/components/icon-button.css b/packages/ui/src/components/icon-button.css index aa550e990f..cb5f61f2bc 100644 --- a/packages/ui/src/components/icon-button.css +++ b/packages/ui/src/components/icon-button.css @@ -120,6 +120,11 @@ gap: calc(var(--spacing) * 0.5); } + &[data-size="small"] { + width: 20px; + height: 20px; + } + &[data-size="large"] { height: 32px; /* padding: 0 8px 0 6px; */ diff --git a/packages/ui/src/components/icon-button.tsx b/packages/ui/src/components/icon-button.tsx index f1832ce7ff..89b8b55063 100644 --- a/packages/ui/src/components/icon-button.tsx +++ b/packages/ui/src/components/icon-button.tsx @@ -4,7 +4,7 @@ import { Icon, IconProps } from "./icon" export interface IconButtonProps extends ComponentProps { icon: IconProps["name"] - size?: "normal" | "large" + size?: "small" | "normal" | "large" iconSize?: IconProps["size"] variant?: "primary" | "secondary" | "ghost" } diff --git a/packages/ui/src/components/markdown.tsx b/packages/ui/src/components/markdown.tsx index e3102214bf..608db818f5 100644 --- a/packages/ui/src/components/markdown.tsx +++ b/packages/ui/src/components/markdown.tsx @@ -69,7 +69,7 @@ function createCopyButton(labels: CopyLabels) { button.type = "button" button.setAttribute("data-component", "icon-button") button.setAttribute("data-variant", "secondary") - button.setAttribute("data-size", "normal") + button.setAttribute("data-size", "small") button.setAttribute("data-slot", "markdown-copy-button") button.setAttribute("aria-label", labels.copy) button.setAttribute("title", labels.copy) diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 2bef792a2c..9a18810dc9 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -121,7 +121,7 @@ [data-slot="text-part-copy-wrapper"] { position: absolute; - top: 8px; + top: -28px; right: 8px; opacity: 0; transition: opacity 0.15s ease; diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 7aad01acea..22aeaa3d58 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -425,6 +425,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp > e.preventDefault()} onClick={(event) => { @@ -694,6 +695,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { > e.preventDefault()} onClick={handleCopy} diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index 840fde05f9..4b8ba8d7a0 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -219,22 +219,28 @@ gap: 4px; align-self: stretch; + [data-slot="session-turn-summary-title-row"] { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + } + [data-slot="session-turn-response"] { - position: relative; width: 100%; } [data-slot="session-turn-response-copy-wrapper"] { - position: absolute; - top: 8px; - right: 8px; opacity: 0; + pointer-events: none; transition: opacity 0.15s ease; - z-index: 1; } - [data-slot="session-turn-response"]:hover [data-slot="session-turn-response-copy-wrapper"] { + &:hover [data-slot="session-turn-response-copy-wrapper"], + &:focus-within [data-slot="session-turn-response-copy-wrapper"] { opacity: 1; + pointer-events: auto; } p { diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 7c5694ba59..5c4678701b 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -702,14 +702,8 @@ export function SessionTurn(
-

{i18n.t("ui.sessionTurn.summary.response")}

-
- +
+

{i18n.t("ui.sessionTurn.summary.response")}

e.preventDefault()} onClick={(event) => { @@ -731,6 +726,14 @@ export function SessionTurn(
+
+ +
From 3116cfc167797a90c8f1a7c5f86d0e2655d3014a Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 5 Feb 2026 07:03:22 -0600 Subject: [PATCH 25/59] chore: package.json scripts --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index e1471d356a..d875b0306b 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "packageManager": "bun@1.3.5", "scripts": { "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", + "dev:desktop": "bun --cwd packages/desktop tauri dev", + "dev:web": "bun --cwd packages/web dev", "typecheck": "bun turbo typecheck", "prepare": "husky", "random": "echo 'Random script'", From 1a6a3f4b54dfd5bb2848d386d4a688524da2a3a2 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 5 Feb 2026 07:04:34 -0600 Subject: [PATCH 26/59] chore: package.json scripts --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d875b0306b..b052e2bbf4 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", "dev:desktop": "bun --cwd packages/desktop tauri dev", - "dev:web": "bun --cwd packages/web dev", + "dev:web": "bun --cwd packages/app dev", "typecheck": "bun turbo typecheck", "prepare": "husky", "random": "echo 'Random script'", From 5b3d94ebaaa013295aad701a612aa2d7f324dd9f Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 5 Feb 2026 06:49:47 -0600 Subject: [PATCH 27/59] fix(app): file tree out of sync --- packages/app/src/context/file.tsx | 23 +++++++++++---------- packages/app/src/context/sdk.tsx | 6 +++--- packages/app/src/pages/directory-layout.tsx | 2 +- packages/app/src/pages/session.tsx | 4 ++-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/app/src/context/file.tsx b/packages/app/src/context/file.tsx index d05e1ee2ad..3ed1b1ae4b 100644 --- a/packages/app/src/context/file.tsx +++ b/packages/app/src/context/file.tsx @@ -1,5 +1,5 @@ -import { createEffect, createMemo, createRoot, onCleanup } from "solid-js" -import { createStore, produce } from "solid-js/store" +import { batch, createEffect, createMemo, createRoot, onCleanup } from "solid-js" +import { createStore, produce, reconcile } from "solid-js/store" import { createSimpleContext } from "@opencode-ai/ui/context" import type { FileContent, FileNode } from "@opencode-ai/sdk/v2" import { showToast } from "@opencode-ai/ui/toast" @@ -8,7 +8,6 @@ import { getFilename } from "@opencode-ai/util/path" import { useSDK } from "./sdk" import { useSync } from "./sync" import { useLanguage } from "@/context/language" -import { decode64 } from "@/utils/base64" import { Persist, persisted } from "@/utils/persist" export type FileSelection = { @@ -276,12 +275,10 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({ const params = useParams() const language = useLanguage() - const directory = createMemo(() => decode64(params.dir) ?? sdk.directory) - - const scope = createMemo(() => directory()) + const scope = createMemo(() => sdk.directory) function normalize(input: string) { - const root = directory() + const root = scope() const prefix = root.endsWith("/") ? root : root + "/" let path = unquoteGitPath(stripQueryAndHash(stripFileProtocol(input))) @@ -372,9 +369,13 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({ inflight.clear() treeInflight.clear() contentLru.clear() - setStore("file", {}) - setTree("node", {}) - setTree("dir", { "": { expanded: true } }) + + batch(() => { + setStore("file", reconcile({})) + setTree("node", reconcile({})) + setTree("dir", reconcile({})) + setTree("dir", "", { expanded: true }) + }) }) const viewCache = new Map() @@ -415,7 +416,7 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({ return entry.value } - const view = createMemo(() => loadView(directory(), params.id)) + const view = createMemo(() => loadView(scope(), params.id)) function ensure(path: string) { if (!path) return diff --git a/packages/app/src/context/sdk.tsx b/packages/app/src/context/sdk.tsx index 123aa4e73e..3a404ec93a 100644 --- a/packages/app/src/context/sdk.tsx +++ b/packages/app/src/context/sdk.tsx @@ -1,17 +1,17 @@ import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2/client" import { createSimpleContext } from "@opencode-ai/ui/context" import { createGlobalEmitter } from "@solid-primitives/event-bus" -import { createEffect, createMemo, onCleanup } from "solid-js" +import { createEffect, createMemo, onCleanup, type Accessor } from "solid-js" import { useGlobalSDK } from "./global-sdk" import { usePlatform } from "./platform" export const { use: useSDK, provider: SDKProvider } = createSimpleContext({ name: "SDK", - init: (props: { directory: string }) => { + init: (props: { directory: Accessor }) => { const platform = usePlatform() const globalSDK = useGlobalSDK() - const directory = createMemo(() => props.directory) + const directory = createMemo(props.directory) const client = createMemo(() => createOpencodeClient({ baseUrl: globalSDK.url, diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx index 037b08c723..da4667a827 100644 --- a/packages/app/src/pages/directory-layout.tsx +++ b/packages/app/src/pages/directory-layout.tsx @@ -31,7 +31,7 @@ export default function Layout(props: ParentProps) { }) return ( - + {iife(() => { const sync = useSync() diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 7af99d8936..7f2727156d 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -490,7 +490,7 @@ export default function Page() { createEffect( on( - () => params.id, + sessionKey, () => setTitle({ draft: "", editing: false, saving: false, menuOpen: false, pendingRename: false }), { defer: true }, ), @@ -1672,7 +1672,7 @@ export default function Page() { createEffect( on( - () => params.dir, + () => sdk.directory, () => { void file.tree.list("") From aedd85d885d565b1e00a6dd3868ca500cbe8d1e6 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 5 Feb 2026 07:04:03 -0600 Subject: [PATCH 28/59] fix(app): file changes not always available --- packages/app/src/pages/session.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 7f2727156d..0bdf5f7f39 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1649,7 +1649,7 @@ export default function Page() { const id = params.id if (!id) return - const wants = isDesktop() ? layout.fileTree.opened() && fileTreeTab() === "changes" : store.mobileTab === "changes" + const wants = isDesktop() ? layout.fileTree.opened() : store.mobileTab === "changes" if (!wants) return if (sync.data.session_diff[id] !== undefined) return if (sync.status === "loading") return From 1fe1457cfa0d908f2718bd6afee74ea7d8d3db0d Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 5 Feb 2026 07:21:46 -0600 Subject: [PATCH 29/59] chore: cleanup --- packages/app/src/i18n/ar.ts | 16 ---------------- packages/app/src/i18n/br.ts | 16 ---------------- packages/app/src/i18n/da.ts | 16 ---------------- packages/app/src/i18n/de.ts | 16 ---------------- packages/app/src/i18n/es.ts | 16 ---------------- packages/app/src/i18n/fr.ts | 16 ---------------- packages/app/src/i18n/ja.ts | 16 ---------------- packages/app/src/i18n/ko.ts | 16 ---------------- packages/app/src/i18n/no.ts | 16 ---------------- packages/app/src/i18n/pl.ts | 16 ---------------- packages/app/src/i18n/ru.ts | 16 ---------------- packages/app/src/i18n/th.ts | 16 ---------------- packages/app/src/i18n/zh.ts | 16 ---------------- packages/app/src/i18n/zht.ts | 16 ---------------- 14 files changed, 224 deletions(-) diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 6af69c8dfd..35f805dbc6 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -321,22 +321,6 @@ export const dict = { "context.usage.clickToView": "انقر لعرض السياق", "context.usage.view": "عرض استخدام السياق", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "لغة", "toast.language.description": "تم التبديل إلى {{language}}", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index fa879eef03..dc8969f7b9 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -320,22 +320,6 @@ export const dict = { "context.usage.clickToView": "Clique para ver o contexto", "context.usage.view": "Ver uso do contexto", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "Idioma", "toast.language.description": "Alterado para {{language}}", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 390ac4c4c4..106ddcf6ff 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -322,22 +322,6 @@ export const dict = { "context.usage.clickToView": "Klik for at se kontekst", "context.usage.view": "Se kontekstforbrug", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "Sprog", "toast.language.description": "Skiftede til {{language}}", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index e1f87a5edc..a240e54750 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -328,22 +328,6 @@ export const dict = { "context.usage.clickToView": "Klicken, um Kontext anzuzeigen", "context.usage.view": "Kontextnutzung anzeigen", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "Sprache", "toast.language.description": "Zu {{language}} gewechselt", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 07b9af7e75..c94f407c6c 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -323,22 +323,6 @@ export const dict = { "context.usage.clickToView": "Haz clic para ver contexto", "context.usage.view": "Ver uso del contexto", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "Idioma", "toast.language.description": "Cambiado a {{language}}", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index c041131798..f36d228045 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -323,22 +323,6 @@ export const dict = { "context.usage.clickToView": "Cliquez pour voir le contexte", "context.usage.view": "Voir l'utilisation du contexte", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "Langue", "toast.language.description": "Passé à {{language}}", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 5cb9a1502d..c4ce4c40d4 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -321,22 +321,6 @@ export const dict = { "context.usage.clickToView": "クリックしてコンテキストを表示", "context.usage.view": "コンテキスト使用量を表示", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "言語", "toast.language.description": "{{language}}に切り替えました", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 05918acba8..2a3f4ef815 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -324,22 +324,6 @@ export const dict = { "context.usage.clickToView": "컨텍스트를 보려면 클릭", "context.usage.view": "컨텍스트 사용량 보기", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "언어", "toast.language.description": "{{language}}(으)로 전환됨", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 4e0fbf64d3..315b21f2cc 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -324,22 +324,6 @@ export const dict = { "context.usage.clickToView": "Klikk for å se kontekst", "context.usage.view": "Se kontekstforbruk", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "Språk", "toast.language.description": "Byttet til {{language}}", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index e395863472..46a727448a 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -322,22 +322,6 @@ export const dict = { "context.usage.clickToView": "Kliknij, aby zobaczyć kontekst", "context.usage.view": "Pokaż użycie kontekstu", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "Język", "toast.language.description": "Przełączono na {{language}}", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 301043f3a5..e4f8b1eaae 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -323,22 +323,6 @@ export const dict = { "context.usage.clickToView": "Нажмите для просмотра контекста", "context.usage.view": "Показать использование контекста", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "Язык", "toast.language.description": "Переключено на {{language}}", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 7947a9d491..c81b1dff3c 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -326,22 +326,6 @@ export const dict = { "context.usage.clickToView": "คลิกเพื่อดูบริบท", "context.usage.view": "ดูการใช้บริบท", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "ภาษา", "toast.language.description": "สลับไปที่ {{language}}", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index a9a94a1ad4..c3b87525cf 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -324,22 +324,6 @@ export const dict = { "context.usage.clickToView": "点击查看上下文", "context.usage.view": "查看上下文用量", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "语言", "toast.language.description": "已切换到{{language}}", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 5407cf477e..7be29f036e 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -321,22 +321,6 @@ export const dict = { "context.usage.clickToView": "點擊查看上下文", "context.usage.view": "檢視上下文用量", - "language.en": "English", - "language.zh": "简体中文", - "language.zht": "繁體中文", - "language.ko": "한국어", - "language.de": "Deutsch", - "language.es": "Español", - "language.fr": "Français", - "language.da": "Dansk", - "language.ja": "日本語", - "language.pl": "Polski", - "language.ru": "Русский", - "language.ar": "العربية", - "language.no": "Norsk", - "language.br": "Português (Brasil)", - "language.th": "ไทย", - "toast.language.title": "語言", "toast.language.description": "已切換到 {{language}}", From a1c46e05ee628f16f92ead49c956b5c0bec2783a Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 5 Feb 2026 10:07:59 -0500 Subject: [PATCH 30/59] core: fix plugin installation to use direct package.json manipulation instead of bun add This ensures plugins install more reliably by writing dependencies directly to package.json rather than relying on bun add commands which can fail in certain environments. Also adds a small delay to ensure filesystem operations complete before proceeding. --- packages/opencode/src/config/config.ts | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index dfb86dbe26..ed1b155003 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -249,29 +249,22 @@ export namespace Config { export async function installDependencies(dir: string) { const pkg = path.join(dir, "package.json") - const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION + const targetVersion = Installation.isLocal() ? "*" : Installation.VERSION - if (!(await Bun.file(pkg).exists())) { - await Bun.write(pkg, "{}") + const json = await Bun.file(pkg) + .json() + .catch(() => ({})) + json.dependencies = { + ...json.dependencies, + "@opencode-ai/plugin": targetVersion, } + await Bun.write(pkg, JSON.stringify(json, null, 2)) + await new Promise((resolve) => setTimeout(resolve, 3000)) const gitignore = path.join(dir, ".gitignore") const hasGitIgnore = await Bun.file(gitignore).exists() if (!hasGitIgnore) await Bun.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n")) - await BunProc.run( - [ - "add", - `@opencode-ai/plugin@${targetVersion}`, - "--exact", - // TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936) - ...(proxied() ? ["--no-cache"] : []), - ], - { - cwd: dir, - }, - ).catch(() => {}) - // Install any additional dependencies defined in the package.json // This allows local plugins and custom tools to use external packages await BunProc.run( From 531b1941a0eaac5faa1e13daae91a0fcfc4c0e13 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 5 Feb 2026 10:13:52 -0500 Subject: [PATCH 31/59] ci: ensure config test waits for dependencies to complete installation --- packages/opencode/test/config/config.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 1014a49687..dee6331106 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -616,6 +616,7 @@ test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => { directory: tmp.path, fn: async () => { await Config.get() + await Config.waitForDependencies() }, }) From 9adcf524e2e64f13754e7e9f07149ca888f4175a Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 5 Feb 2026 11:29:00 -0500 Subject: [PATCH 32/59] core: bundle GitLab auth plugin directly instead of dynamic install Remove dynamic installation of built-in plugins. GitLab auth is now imported directly as an internal plugin, eliminating network requests during startup and simplifying the plugin loading logic. Removes the need for test mocks since plugins are no longer dynamically installed at runtime. --- bun.lock | 79 +++++++++++++++++++ packages/opencode/package.json | 1 + packages/opencode/src/config/config.ts | 2 +- packages/opencode/src/plugin/index.ts | 30 +------ .../test/provider/amazon-bedrock.test.ts | 46 ++--------- .../opencode/test/provider/gitlab-duo.test.ts | 36 ++------- .../opencode/test/provider/provider.test.ts | 23 +----- 7 files changed, 97 insertions(+), 120 deletions(-) diff --git a/bun.lock b/bun.lock index 91b9fa3399..dd83ee01ff 100644 --- a/bun.lock +++ b/bun.lock @@ -287,6 +287,7 @@ "@ai-sdk/xai": "2.0.51", "@clack/prompts": "1.0.0-alpha.1", "@gitlab/gitlab-ai-provider": "3.4.0", + "@gitlab/opencode-gitlab-auth": "1.3.2", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", "@modelcontextprotocol/sdk": "1.25.2", @@ -920,8 +921,22 @@ "@expressive-code/plugin-text-markers": ["@expressive-code/plugin-text-markers@0.41.6", "", { "dependencies": { "@expressive-code/core": "^0.41.6" } }, "sha512-PBFa1wGyYzRExMDzBmAWC6/kdfG1oLn4pLpBeTfIRrALPjcGA/59HP3e7q9J0Smk4pC7U+lWkA2LHR8FYV8U7Q=="], + "@fastify/ajv-compiler": ["@fastify/ajv-compiler@4.0.5", "", { "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0" } }, "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A=="], + "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], + "@fastify/error": ["@fastify/error@4.2.0", "", {}, "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ=="], + + "@fastify/fast-json-stringify-compiler": ["@fastify/fast-json-stringify-compiler@5.0.3", "", { "dependencies": { "fast-json-stringify": "^6.0.0" } }, "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ=="], + + "@fastify/forwarded": ["@fastify/forwarded@3.0.1", "", {}, "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw=="], + + "@fastify/merge-json-schemas": ["@fastify/merge-json-schemas@0.2.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A=="], + + "@fastify/proxy-addr": ["@fastify/proxy-addr@5.1.0", "", { "dependencies": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" } }, "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw=="], + + "@fastify/rate-limit": ["@fastify/rate-limit@10.3.0", "", { "dependencies": { "@lukeed/ms": "^2.0.2", "fastify-plugin": "^5.0.0", "toad-cache": "^3.7.0" } }, "sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q=="], + "@floating-ui/core": ["@floating-ui/core@1.7.4", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="], "@floating-ui/dom": ["@floating-ui/dom@1.7.5", "", { "dependencies": { "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg=="], @@ -936,6 +951,8 @@ "@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.4.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-1fEZgqjSZ0WLesftw/J5UtFuJCYFDvCZCHhTH5PZAmpDEmCwllJBoe84L3+vIk38V2FGDMTW128iKTB2mVzr3A=="], + "@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.2", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-pvGrC+aDVLY8bRCC/fZaG/Qihvt2r4by5xbTo5JTSz9O7yIcR6xG2d9Wkuu4bcXFz674z2C+i5bUk+J/RSdBpg=="], + "@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="], "@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="], @@ -1128,6 +1145,8 @@ "@leichtgewicht/ip-codec": ["@leichtgewicht/ip-codec@2.0.5", "", {}, "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="], + "@lukeed/ms": ["@lukeed/ms@2.0.2", "", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="], + "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="], @@ -1368,6 +1387,8 @@ "@pierre/diffs": ["@pierre/diffs@1.0.2", "", { "dependencies": { "@shikijs/core": "^3.0.0", "@shikijs/engine-javascript": "3.19.0", "@shikijs/transformers": "3.19.0", "diff": "8.0.2", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "3.19.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-RkFSDD5X/U+8QjyilPViYGJfmJNWXR17zTL8zw48+DcVC1Ujbh6I1edyuRnFfgRzpft05x2DSCkz2cjoIAxPvQ=="], + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@planetscale/database": ["@planetscale/database@1.19.0", "", {}, "sha512-Tv4jcFUFAFjOWrGSio49H6R2ijALv0ZzVBfJKIdm+kl9X046Fh4LLawrF9OMsglVbK6ukqMJsUCeucGAFTBcMA=="], @@ -1944,6 +1965,8 @@ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + "abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="], + "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], @@ -2020,10 +2043,14 @@ "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + "autoprefixer": ["autoprefixer@10.4.23", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001760", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + "avvio": ["avvio@9.1.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw=="], + "await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="], "aws-sdk": ["aws-sdk@2.1692.0", "", { "dependencies": { "buffer": "4.9.2", "events": "1.1.1", "ieee754": "1.1.13", "jmespath": "0.16.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", "util": "^0.12.4", "uuid": "8.0.0", "xml2js": "0.6.2" } }, "sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw=="], @@ -2466,16 +2493,26 @@ "fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="], + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + "fast-json-stringify": ["fast-json-stringify@6.2.0", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-Eaf/KNIDwHkzfyeQFNfLXJnQ7cl1XQI3+zRqmPlvtkMigbXnAcasTrvJQmquBSxKfFGeRA6PFog8t+hFmpDoWw=="], + + "fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="], + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], "fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="], + "fastify": ["fastify@5.7.4", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^10.1.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA=="], + + "fastify-plugin": ["fastify-plugin@5.1.0", "", {}, "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw=="], + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -2490,6 +2527,8 @@ "find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="], + "find-my-way": ["find-my-way@9.4.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, "sha512-5Ye4vHsypZRYtS01ob/iwHzGRUDELlsoCftI/OZFhcLs1M0tkGPcXldE80TAZC5yYuJMBPJQQ43UHlqbJWiX2w=="], + "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "finity": ["finity@0.5.4", "", {}, "sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA=="], @@ -2856,6 +2895,8 @@ "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + "json-schema-ref-resolver": ["json-schema-ref-resolver@3.0.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A=="], + "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], @@ -2892,6 +2933,8 @@ "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], + "light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="], + "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], @@ -3194,6 +3237,8 @@ "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="], + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], @@ -3300,6 +3345,12 @@ "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], + "pino": ["pino@10.3.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA=="], + + "pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="], + + "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="], + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], "pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="], @@ -3352,6 +3403,8 @@ "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], + "promise.allsettled": ["promise.allsettled@1.0.7", "", { "dependencies": { "array.prototype.map": "^1.0.5", "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", "get-intrinsic": "^1.2.1", "iterate-value": "^1.0.2" } }, "sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA=="], "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], @@ -3374,6 +3427,8 @@ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], @@ -3408,6 +3463,8 @@ "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + "recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="], "recma-jsx": ["recma-jsx@1.0.1", "", { "dependencies": { "acorn-jsx": "^5.0.0", "estree-util-to-js": "^2.0.0", "recma-parse": "^1.0.0", "recma-stringify": "^1.0.0", "unified": "^11.0.0" }, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w=="], @@ -3474,6 +3531,8 @@ "restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="], + "ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="], + "retext": ["retext@9.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" } }, "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA=="], "retext-latin": ["retext-latin@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "parse-latin": "^7.0.0", "unified": "^11.0.0" } }, "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA=="], @@ -3486,6 +3545,8 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], "rollup": ["rollup@4.57.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.0", "@rollup/rollup-android-arm64": "4.57.0", "@rollup/rollup-darwin-arm64": "4.57.0", "@rollup/rollup-darwin-x64": "4.57.0", "@rollup/rollup-freebsd-arm64": "4.57.0", "@rollup/rollup-freebsd-x64": "4.57.0", "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", "@rollup/rollup-linux-arm-musleabihf": "4.57.0", "@rollup/rollup-linux-arm64-gnu": "4.57.0", "@rollup/rollup-linux-arm64-musl": "4.57.0", "@rollup/rollup-linux-loong64-gnu": "4.57.0", "@rollup/rollup-linux-loong64-musl": "4.57.0", "@rollup/rollup-linux-ppc64-gnu": "4.57.0", "@rollup/rollup-linux-ppc64-musl": "4.57.0", "@rollup/rollup-linux-riscv64-gnu": "4.57.0", "@rollup/rollup-linux-riscv64-musl": "4.57.0", "@rollup/rollup-linux-s390x-gnu": "4.57.0", "@rollup/rollup-linux-x64-gnu": "4.57.0", "@rollup/rollup-linux-x64-musl": "4.57.0", "@rollup/rollup-openbsd-x64": "4.57.0", "@rollup/rollup-openharmony-arm64": "4.57.0", "@rollup/rollup-win32-arm64-msvc": "4.57.0", "@rollup/rollup-win32-ia32-msvc": "4.57.0", "@rollup/rollup-win32-x64-gnu": "4.57.0", "@rollup/rollup-win32-x64-msvc": "4.57.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA=="], @@ -3508,6 +3569,10 @@ "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + "safe-regex2": ["safe-regex2@5.0.0", "", { "dependencies": { "ret": "~0.5.0" } }, "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "sax": ["sax@1.2.1", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="], @@ -3516,6 +3581,8 @@ "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], + "secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="], + "selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="], "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], @@ -3530,6 +3597,8 @@ "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], @@ -3592,6 +3661,8 @@ "solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="], + "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="], + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -3600,6 +3671,8 @@ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], @@ -3702,6 +3775,8 @@ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + "thread-stream": ["thread-stream@4.0.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA=="], + "three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="], "thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="], @@ -4084,6 +4159,8 @@ "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + "@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], + "@gitlab/gitlab-ai-provider/openai": ["openai@6.17.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-NHRpPEUPzAvFOAFs9+9pC6+HCw/iWsYsKCMPXH5Kw7BpMxqd8g/A07/1o7Gx2TWtCnzevVRyKMRFqyiHyAlqcA=="], "@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -4384,6 +4461,8 @@ "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="], + "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index a0d6892f9d..7032245acc 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -72,6 +72,7 @@ "@ai-sdk/xai": "2.0.51", "@clack/prompts": "1.0.0-alpha.1", "@gitlab/gitlab-ai-provider": "3.4.0", + "@gitlab/opencode-gitlab-auth": "1.3.2", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", "@modelcontextprotocol/sdk": "1.25.2", diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index ed1b155003..e1110e0927 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -274,7 +274,7 @@ export namespace Config { ...(proxied() ? ["--no-cache"] : []), ], { cwd: dir }, - ).catch(() => {}) + ) } async function isWritable(dir: string) { diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 4e5776e51d..627c6f8cf0 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -6,19 +6,15 @@ import { createOpencodeClient } from "@opencode-ai/sdk" import { Server } from "../server/server" import { BunProc } from "../bun" import { Instance } from "../project/instance" -import { Flag } from "../flag/flag" import { CodexAuthPlugin } from "./codex" -import { Session } from "../session" -import { NamedError } from "@opencode-ai/util/error" import { CopilotAuthPlugin } from "./copilot" +import { gitlabAuthPlugin as GitlabAuthPlugin } from "@gitlab/opencode-gitlab-auth" export namespace Plugin { const log = Log.create({ service: "plugin" }) - const BUILTIN = ["opencode-anthropic-auth@0.0.13", "@gitlab/opencode-gitlab-auth@1.3.2"] - // Built-in plugins that are directly imported (not installed from npm) - const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin, CopilotAuthPlugin] + const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin, CopilotAuthPlugin, GitlabAuthPlugin] const state = Instance.state(async () => { const client = createOpencodeClient({ @@ -45,9 +41,6 @@ export namespace Plugin { const plugins = [...(config.plugin ?? [])] if (plugins.length) await Config.waitForDependencies() - if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) { - plugins.push(...BUILTIN) - } for (let plugin of plugins) { // ignore old codex plugin since it is supported first party now @@ -57,24 +50,7 @@ export namespace Plugin { const lastAtIndex = plugin.lastIndexOf("@") const pkg = lastAtIndex > 0 ? plugin.substring(0, lastAtIndex) : plugin const version = lastAtIndex > 0 ? plugin.substring(lastAtIndex + 1) : "latest" - const builtin = BUILTIN.some((x) => x.startsWith(pkg + "@")) - plugin = await BunProc.install(pkg, version).catch((err) => { - if (!builtin) throw err - - const message = err instanceof Error ? err.message : String(err) - log.error("failed to install builtin plugin", { - pkg, - version, - error: message, - }) - Bus.publish(Session.Event.Error, { - error: new NamedError.Unknown({ - message: `Failed to install built-in plugin ${pkg}@${version}: ${message}`, - }).toObject(), - }) - - return "" - }) + plugin = await BunProc.install(pkg, version) if (!plugin) continue } const mod = await import(plugin) diff --git a/packages/opencode/test/provider/amazon-bedrock.test.ts b/packages/opencode/test/provider/amazon-bedrock.test.ts index a90c9632d9..d1d3cc41c4 100644 --- a/packages/opencode/test/provider/amazon-bedrock.test.ts +++ b/packages/opencode/test/provider/amazon-bedrock.test.ts @@ -1,46 +1,12 @@ -import { test, expect, mock, describe } from "bun:test" +import { test, expect, describe } from "bun:test" import path from "path" import { unlink } from "fs/promises" -// === Mocks === -// These mocks are required because Provider.list() triggers: -// 1. BunProc.install("@aws-sdk/credential-providers") - in bedrock custom loader -// 2. Plugin.list() which calls BunProc.install() for default plugins -// Without mocks, these would attempt real package installations that timeout in tests. - -mock.module("../../src/bun/index", () => ({ - BunProc: { - install: async (pkg: string, _version?: string) => { - // Return package name without version for mocking - const lastAtIndex = pkg.lastIndexOf("@") - return lastAtIndex > 0 ? pkg.substring(0, lastAtIndex) : pkg - }, - run: async () => { - throw new Error("BunProc.run should not be called in tests") - }, - which: () => process.execPath, - InstallFailedError: class extends Error {}, - }, -})) - -mock.module("@aws-sdk/credential-providers", () => ({ - fromNodeProviderChain: () => async () => ({ - accessKeyId: "mock-access-key-id", - secretAccessKey: "mock-secret-access-key", - }), -})) - -const mockPlugin = () => ({}) -mock.module("opencode-copilot-auth", () => ({ default: mockPlugin })) -mock.module("opencode-anthropic-auth", () => ({ default: mockPlugin })) -mock.module("@gitlab/opencode-gitlab-auth", () => ({ default: mockPlugin })) - -// Import after mocks are set up -const { tmpdir } = await import("../fixture/fixture") -const { Instance } = await import("../../src/project/instance") -const { Provider } = await import("../../src/provider/provider") -const { Env } = await import("../../src/env") -const { Global } = await import("../../src/global") +import { tmpdir } from "../fixture/fixture" +import { Instance } from "../../src/project/instance" +import { Provider } from "../../src/provider/provider" +import { Env } from "../../src/env" +import { Global } from "../../src/global" test("Bedrock: config region takes precedence over AWS_REGION env var", async () => { await using tmp = await tmpdir({ diff --git a/packages/opencode/test/provider/gitlab-duo.test.ts b/packages/opencode/test/provider/gitlab-duo.test.ts index 4d5aa9c746..c512a45909 100644 --- a/packages/opencode/test/provider/gitlab-duo.test.ts +++ b/packages/opencode/test/provider/gitlab-duo.test.ts @@ -1,35 +1,11 @@ -import { test, expect, mock } from "bun:test" +import { test, expect } from "bun:test" import path from "path" -// === Mocks === -// These mocks prevent real package installations during tests - -mock.module("../../src/bun/index", () => ({ - BunProc: { - install: async (pkg: string, _version?: string) => { - // Return package name without version for mocking - const lastAtIndex = pkg.lastIndexOf("@") - return lastAtIndex > 0 ? pkg.substring(0, lastAtIndex) : pkg - }, - run: async () => { - throw new Error("BunProc.run should not be called in tests") - }, - which: () => process.execPath, - InstallFailedError: class extends Error {}, - }, -})) - -const mockPlugin = () => ({}) -mock.module("opencode-copilot-auth", () => ({ default: mockPlugin })) -mock.module("opencode-anthropic-auth", () => ({ default: mockPlugin })) -mock.module("@gitlab/opencode-gitlab-auth", () => ({ default: mockPlugin })) - -// Import after mocks are set up -const { tmpdir } = await import("../fixture/fixture") -const { Instance } = await import("../../src/project/instance") -const { Provider } = await import("../../src/provider/provider") -const { Env } = await import("../../src/env") -const { Global } = await import("../../src/global") +import { tmpdir } from "../fixture/fixture" +import { Instance } from "../../src/project/instance" +import { Provider } from "../../src/provider/provider" +import { Env } from "../../src/env" +import { Global } from "../../src/global" test("GitLab Duo: loads provider with API key from environment", async () => { await using tmp = await tmpdir({ diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 482587d8ac..98cd49c02f 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1,27 +1,6 @@ -import { test, expect, mock } from "bun:test" +import { test, expect } from "bun:test" import path from "path" -// Mock BunProc and default plugins to prevent actual installations during tests -mock.module("../../src/bun/index", () => ({ - BunProc: { - install: async (pkg: string, _version?: string) => { - // Return package name without version for mocking - const lastAtIndex = pkg.lastIndexOf("@") - return lastAtIndex > 0 ? pkg.substring(0, lastAtIndex) : pkg - }, - run: async () => { - throw new Error("BunProc.run should not be called in tests") - }, - which: () => process.execPath, - InstallFailedError: class extends Error {}, - }, -})) - -const mockPlugin = () => ({}) -mock.module("opencode-copilot-auth", () => ({ default: mockPlugin })) -mock.module("opencode-anthropic-auth", () => ({ default: mockPlugin })) -mock.module("@gitlab/opencode-gitlab-auth", () => ({ default: mockPlugin })) - import { tmpdir } from "../fixture/fixture" import { Instance } from "../../src/project/instance" import { Provider } from "../../src/provider/provider" From dbde377ab0a5bef51b3814c13b4cf6605ffbfbcb Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 5 Feb 2026 16:45:41 +0000 Subject: [PATCH 33/59] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index 9ba6b762c6..e4ef98a0d4 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-OJ4a65oeJpSXfmL2tNslGhgvo1WPNBLL/5pWlDOK0jY=", - "aarch64-linux": "sha256-DoJcXOKb/SNN78Fp4z3sQSlzPLaUQ0Ss/jZa2tL6hdc=", - "aarch64-darwin": "sha256-jtMqFH7uYzgI1ICASAePJOj1QeaQfEWuoME7gyrvinA=", - "x86_64-darwin": "sha256-doY5oQKtOlmF4aegKvchU1M86zDjGNfMwE+GvfpZa0g=" + "x86_64-linux": "sha256-/wjmXpex5zfBZBfc2Zszh9UurRuKjm8S+gdAkMrWL98=", + "aarch64-linux": "sha256-c254hgVVyLFucacOQlpnJ+3eCw8xIK9+388EigiuKeM=", + "aarch64-darwin": "sha256-A71PII7Ue0uqsH970GPi0XfRKInpSFFom8jD3Q0wrgQ=", + "x86_64-darwin": "sha256-M9q8O0GkiNoDPMW4C3EVvLo+X9mjQYLJTtNZx5AuQCc=" } } From fba5a79c45716989dc0f70d8960324d2a6455b1b Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:40:29 -0600 Subject: [PATCH 34/59] fix: ensure that github copilot plugin properly sets headers when used in other clients than tui (#12316) --- packages/opencode/src/plugin/copilot.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/plugin/copilot.ts b/packages/opencode/src/plugin/copilot.ts index ef41ee38d3..39ea0d00d2 100644 --- a/packages/opencode/src/plugin/copilot.ts +++ b/packages/opencode/src/plugin/copilot.ts @@ -301,17 +301,20 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise { }, ], }, - "chat.headers": async (input, output) => { - if (!input.model.providerID.includes("github-copilot")) return + "chat.headers": async (incoming, output) => { + if (!incoming.model.providerID.includes("github-copilot")) return - if (input.model.api.npm === "@ai-sdk/anthropic") { + if (incoming.model.api.npm === "@ai-sdk/anthropic") { output.headers["anthropic-beta"] = "interleaved-thinking-2025-05-14" } const session = await sdk.session .get({ path: { - id: input.sessionID, + id: incoming.sessionID, + }, + query: { + directory: input.directory, }, throwOnError: true, }) From 7c748ef08925e8dfba364e3d6388537d4e0c1ea9 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 5 Feb 2026 12:47:32 -0500 Subject: [PATCH 35/59] core: silently ignore proxy command failures to prevent config initialization crashes --- packages/opencode/src/config/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index e1110e0927..ed1b155003 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -274,7 +274,7 @@ export namespace Config { ...(proxied() ? ["--no-cache"] : []), ], { cwd: dir }, - ) + ).catch(() => {}) } async function isWritable(dir: string) { From e08705f4ef128f20912925de9d08c41515a9709b Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 5 Feb 2026 13:01:48 -0500 Subject: [PATCH 36/59] zen: opus 4.6 --- packages/web/src/content/docs/zen.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 27f4c229c5..f9b7e68cdb 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -77,6 +77,7 @@ You can also access our models through the following API endpoints. | Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` | @@ -132,6 +133,8 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 | | Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 | | Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 | +| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 | +| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | | Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | From 40ebc34909fdb45a0cce5f73725f52ae5dcf8509 Mon Sep 17 00:00:00 2001 From: Goni Zahavy Date: Thu, 5 Feb 2026 20:23:02 +0200 Subject: [PATCH 37/59] feat(tui): add running spinner to bash tool in TUI (#12317) --- packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 8a38d9e6f1..5aba1a56a5 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -1625,6 +1625,7 @@ function BlockTool(props: { function Bash(props: ToolProps) { const { theme } = useTheme() const sync = useSync() + const isRunning = createMemo(() => props.part.state.status === "running") const output = createMemo(() => stripAnsi(props.metadata.output?.trim() ?? "")) const [expanded, setExpanded] = createSignal(false) const lines = createMemo(() => output().split("\n")) @@ -1665,6 +1666,7 @@ function Bash(props: ToolProps) { setExpanded((prev) => !prev) : undefined} > From 47f00d23b3f1e4ba118b5cfe186b329c4a729d92 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 5 Feb 2026 13:23:48 -0500 Subject: [PATCH 38/59] enable 5.3 codex --- packages/opencode/src/plugin/codex.ts | 1 + packages/opencode/src/plugin/index.ts | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index b6f1a96a9f..7658ad55e5 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -361,6 +361,7 @@ export async function CodexAuthPlugin(input: PluginInput): Promise { "gpt-5.1-codex-mini", "gpt-5.2", "gpt-5.2-codex", + "gpt-5.3-codex", "gpt-5.1-codex", ]) for (const modelId of Object.keys(provider.models)) { diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 627c6f8cf0..7c55970cd0 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -6,13 +6,18 @@ import { createOpencodeClient } from "@opencode-ai/sdk" import { Server } from "../server/server" import { BunProc } from "../bun" import { Instance } from "../project/instance" +import { Flag } from "../flag/flag" import { CodexAuthPlugin } from "./codex" +import { Session } from "../session" +import { NamedError } from "@opencode-ai/util/error" import { CopilotAuthPlugin } from "./copilot" import { gitlabAuthPlugin as GitlabAuthPlugin } from "@gitlab/opencode-gitlab-auth" export namespace Plugin { const log = Log.create({ service: "plugin" }) + const BUILTIN = ["opencode-anthropic-auth@0.0.13"] + // Built-in plugins that are directly imported (not installed from npm) const INTERNAL_PLUGINS: PluginInstance[] = [CodexAuthPlugin, CopilotAuthPlugin, GitlabAuthPlugin] @@ -41,6 +46,9 @@ export namespace Plugin { const plugins = [...(config.plugin ?? [])] if (plugins.length) await Config.waitForDependencies() + if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) { + plugins.push(...BUILTIN) + } for (let plugin of plugins) { // ignore old codex plugin since it is supported first party now @@ -50,7 +58,24 @@ export namespace Plugin { const lastAtIndex = plugin.lastIndexOf("@") const pkg = lastAtIndex > 0 ? plugin.substring(0, lastAtIndex) : plugin const version = lastAtIndex > 0 ? plugin.substring(lastAtIndex + 1) : "latest" - plugin = await BunProc.install(pkg, version) + const builtin = BUILTIN.some((x) => x.startsWith(pkg + "@")) + plugin = await BunProc.install(pkg, version).catch((err) => { + if (!builtin) throw err + + const message = err instanceof Error ? err.message : String(err) + log.error("failed to install builtin plugin", { + pkg, + version, + error: message, + }) + Bus.publish(Session.Event.Error, { + error: new NamedError.Unknown({ + message: `Failed to install built-in plugin ${pkg}@${version}: ${message}`, + }).toObject(), + }) + + return "" + }) if (!plugin) continue } const mod = await import(plugin) From a0bc65621532a5dfcd41fa08d62dbcaf093a924d Mon Sep 17 00:00:00 2001 From: opencode Date: Thu, 5 Feb 2026 18:48:54 +0000 Subject: [PATCH 39/59] release: v1.1.52 --- bun.lock | 30 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++------ packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 18 files changed, 37 insertions(+), 37 deletions(-) diff --git a/bun.lock b/bun.lock index dd83ee01ff..798822af55 100644 --- a/bun.lock +++ b/bun.lock @@ -23,7 +23,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -73,7 +73,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -107,7 +107,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -134,7 +134,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -158,7 +158,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -182,7 +182,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -213,7 +213,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -242,7 +242,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -258,7 +258,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.1.51", + "version": "1.1.52", "bin": { "opencode": "./bin/opencode", }, @@ -364,7 +364,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -384,7 +384,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.1.51", + "version": "1.1.52", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -395,7 +395,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -408,7 +408,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -450,7 +450,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "zod": "catalog:", }, @@ -461,7 +461,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index 3a492ed919..81e589e38b 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.1.51", + "version": "1.1.52", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index d162f1ab0c..045363ea14 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.1.51", + "version": "1.1.52", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 3f69947016..256a817d0f 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.1.51", + "version": "1.1.52", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index efe6dc4256..19e9da661f 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.1.51", + "version": "1.1.52", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index 9da4390c58..5e328cfb6f 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.1.51", + "version": "1.1.52", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 92676ec596..5991ace378 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.1.51", + "version": "1.1.52", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 13008747eb..0663f98ed0 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.1.51", + "version": "1.1.52", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index eeadfa04f0..8df70b6434 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.1.51" +version = "1.1.52" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/anomalyco/opencode" @@ -11,26 +11,26 @@ name = "OpenCode" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.51/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.52/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.51/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.52/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.51/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.52/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.51/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.52/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.51/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.52/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 110525c80f..e029196d4c 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.1.51", + "version": "1.1.52", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 7032245acc..9c6644e3ce 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.1.51", + "version": "1.1.52", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index d8b8733cb3..86275fc775 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.1.51", + "version": "1.1.52", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 19f4401a64..bda7bbe866 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.1.51", + "version": "1.1.52", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/slack/package.json b/packages/slack/package.json index 0c97a26c55..9522f33a20 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.1.51", + "version": "1.1.52", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 83902a10f9..c2462c4896 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.1.51", + "version": "1.1.52", "type": "module", "license": "MIT", "exports": { diff --git a/packages/util/package.json b/packages/util/package.json index f048583f75..2be458dfb1 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.1.51", + "version": "1.1.52", "private": true, "type": "module", "license": "MIT", diff --git a/packages/web/package.json b/packages/web/package.json index 1b405bc6b0..4e0cafc1bd 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.1.51", + "version": "1.1.52", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 013caf7dbc..5b36c06ff9 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.1.51", + "version": "1.1.52", "publisher": "sst-dev", "repository": { "type": "git", From 2f78705f6e91b6a775d544460246cea59b2a4068 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:49:40 -0600 Subject: [PATCH 40/59] tweak: update transforms for gpt-5.3 (#12325) --- packages/opencode/src/provider/transform.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index c1846af700..e564c54a1e 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -375,7 +375,8 @@ export namespace ProviderTransform { } } const copilotEfforts = iife(() => { - if (id.includes("5.1-codex-max") || id.includes("5.2")) return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"] + if (id.includes("5.1-codex-max") || id.includes("5.2") || id.includes("5.3")) + return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"] return WIDELY_SUPPORTED_EFFORTS }) return Object.fromEntries( @@ -422,7 +423,7 @@ export namespace ProviderTransform { if (id === "gpt-5-pro") return {} const openaiEfforts = iife(() => { if (id.includes("codex")) { - if (id.includes("5.2")) return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"] + if (id.includes("5.2") || id.includes("5.3")) return [...WIDELY_SUPPORTED_EFFORTS, "xhigh"] return WIDELY_SUPPORTED_EFFORTS } const arr = [...WIDELY_SUPPORTED_EFFORTS] From 8ddef975b729e4c256c880b7813905240fb0d68e Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Thu, 5 Feb 2026 19:58:09 +0100 Subject: [PATCH 41/59] feat(acp): add session usage (#12299) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- bun.lock | 4 +- packages/opencode/package.json | 2 +- packages/opencode/src/acp/agent.ts | 129 ++++++++++++++++++++++++++--- 3 files changed, 121 insertions(+), 14 deletions(-) diff --git a/bun.lock b/bun.lock index 798822af55..5b4616ae7d 100644 --- a/bun.lock +++ b/bun.lock @@ -265,7 +265,7 @@ "dependencies": { "@actions/core": "1.11.1", "@actions/github": "6.0.1", - "@agentclientprotocol/sdk": "0.13.0", + "@agentclientprotocol/sdk": "0.14.1", "@ai-sdk/amazon-bedrock": "3.0.74", "@ai-sdk/anthropic": "2.0.58", "@ai-sdk/azure": "2.0.91", @@ -559,7 +559,7 @@ "@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="], - "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.13.0", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Z6/Fp4cXLbYdMXr5AK752JM5qG2VKb6ShM0Ql6FimBSckMmLyK54OA20UhPYoH4C37FSFwUTARuwQOwQUToYrw=="], + "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.14.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-b6r3PS3Nly+Wyw9U+0nOr47bV8tfS476EgyEMhoKvJCZLbgqoDFN7DJwkxL88RR0aiOqOYV1ZnESHqb+RmdH8w=="], "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.74", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.58", "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.20", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-q83HE3FBb/HPIvjXsehrHOgCuGHPorSMFt6BYnzIYZy8gNnSqV1OWX4oXVsCAuYPPMtYW/KMK35hmoIFV8QKoQ=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 9c6644e3ce..6d52c7609a 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -50,7 +50,7 @@ "dependencies": { "@actions/core": "1.11.1", "@actions/github": "6.0.1", - "@agentclientprotocol/sdk": "0.13.0", + "@agentclientprotocol/sdk": "0.14.1", "@ai-sdk/amazon-bedrock": "3.0.74", "@ai-sdk/anthropic": "2.0.58", "@ai-sdk/azure": "2.0.91", diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index cc9a029a04..775acc52a5 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -25,6 +25,7 @@ import { type SetSessionModeResponse, type ToolCallContent, type ToolKind, + type Usage, } from "@agentclientprotocol/sdk" import { Log } from "../util/log" @@ -38,7 +39,7 @@ import { Config } from "@/config/config" import { Todo } from "@/session/todo" import { z } from "zod" import { LoadAPIKeyError } from "ai" -import type { Event, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2" +import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2" import { applyPatch } from "diff" type ModeOption = { id: string; name: string; description?: string } @@ -49,6 +50,74 @@ const DEFAULT_VARIANT_VALUE = "default" export namespace ACP { const log = Log.create({ service: "acp-agent" }) + async function getContextLimit( + sdk: OpencodeClient, + providerID: string, + modelID: string, + directory: string, + ): Promise { + const providers = await sdk.config + .providers({ directory }) + .then((x) => x.data?.providers ?? []) + .catch((error) => { + log.error("failed to get providers for context limit", { error }) + return [] + }) + + const provider = providers.find((p) => p.id === providerID) + const model = provider?.models[modelID] + return model?.limit.context ?? null + } + + async function sendUsageUpdate( + connection: AgentSideConnection, + sdk: OpencodeClient, + sessionID: string, + directory: string, + ): Promise { + const messages = await sdk.session + .messages({ sessionID, directory }, { throwOnError: true }) + .then((x) => x.data) + .catch((error) => { + log.error("failed to fetch messages for usage update", { error }) + return undefined + }) + + if (!messages) return + + const assistantMessages = messages.filter( + (m): m is { info: AssistantMessage; parts: SessionMessageResponse["parts"] } => m.info.role === "assistant", + ) + + const lastAssistant = assistantMessages[assistantMessages.length - 1] + if (!lastAssistant) return + + const msg = lastAssistant.info + const size = await getContextLimit(sdk, msg.providerID, msg.modelID, directory) + + if (!size) { + // Cannot calculate usage without known context size + return + } + + const used = msg.tokens.input + (msg.tokens.cache?.read ?? 0) + const totalCost = assistantMessages.reduce((sum, m) => sum + m.info.cost, 0) + + await connection + .sessionUpdate({ + sessionId: sessionID, + update: { + sessionUpdate: "usage_update", + used, + size, + cost: { amount: totalCost, currency: "USD" }, + }, + }) + .catch((error) => { + log.error("failed to send usage update", { error }) + }) + } + export async function init({ sdk: _sdk }: { sdk: OpencodeClient }) { return { create: (connection: AgentSideConnection, fullConfig: ACPConfig) => { @@ -546,6 +615,8 @@ export namespace ACP { await this.processMessage(msg) } + await sendUsageUpdate(this.connection, this.sdk, sessionId, directory) + return result } catch (e) { const error = MessageV2.fromError(e, { @@ -654,6 +725,8 @@ export namespace ACP { await this.processMessage(msg) } + await sendUsageUpdate(this.connection, this.sdk, sessionId, directory) + return mode } catch (e) { const error = MessageV2.fromError(e, { @@ -677,11 +750,15 @@ export namespace ACP { log.info("resume_session", { sessionId, mcpServers: mcpServers.length }) - return this.loadSessionMode({ + const result = await this.loadSessionMode({ cwd: directory, mcpServers, sessionId, }) + + await sendUsageUpdate(this.connection, this.sdk, sessionId, directory) + + return result } catch (e) { const error = MessageV2.fromError(e, { providerID: this.config.defaultModel?.providerID ?? "unknown", @@ -1239,13 +1316,22 @@ export namespace ACP { return { name, args: rest.join(" ").trim() } })() - const done = { - stopReason: "end_turn" as const, - _meta: {}, - } + const buildUsage = (msg: AssistantMessage): Usage => ({ + totalTokens: + msg.tokens.input + + msg.tokens.output + + msg.tokens.reasoning + + (msg.tokens.cache?.read ?? 0) + + (msg.tokens.cache?.write ?? 0), + inputTokens: msg.tokens.input, + outputTokens: msg.tokens.output, + thoughtTokens: msg.tokens.reasoning || undefined, + cachedReadTokens: msg.tokens.cache?.read || undefined, + cachedWriteTokens: msg.tokens.cache?.write || undefined, + }) if (!cmd) { - await this.sdk.session.prompt({ + const response = await this.sdk.session.prompt({ sessionID, model: { providerID: model.providerID, @@ -1256,14 +1342,22 @@ export namespace ACP { agent, directory, }) - return done + const msg = response.data?.info + + await sendUsageUpdate(this.connection, this.sdk, sessionID, directory) + + return { + stopReason: "end_turn" as const, + usage: msg ? buildUsage(msg) : undefined, + _meta: {}, + } } const command = await this.config.sdk.command .list({ directory }, { throwOnError: true }) .then((x) => x.data!.find((c) => c.name === cmd.name)) if (command) { - await this.sdk.session.command({ + const response = await this.sdk.session.command({ sessionID, command: command.name, arguments: cmd.args, @@ -1271,7 +1365,15 @@ export namespace ACP { agent, directory, }) - return done + const msg = response.data?.info + + await sendUsageUpdate(this.connection, this.sdk, sessionID, directory) + + return { + stopReason: "end_turn" as const, + usage: msg ? buildUsage(msg) : undefined, + _meta: {}, + } } switch (cmd.name) { @@ -1288,7 +1390,12 @@ export namespace ACP { break } - return done + await sendUsageUpdate(this.connection, this.sdk, sessionID, directory) + + return { + stopReason: "end_turn" as const, + _meta: {}, + } } async cancel(params: CancelNotification) { From 081f065942e6dc505443f323e0ce78a15838d997 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 5 Feb 2026 19:09:43 +0000 Subject: [PATCH 42/59] chore: update nix node_modules hashes --- nix/hashes.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/hashes.json b/nix/hashes.json index e4ef98a0d4..bc06c23d86 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-/wjmXpex5zfBZBfc2Zszh9UurRuKjm8S+gdAkMrWL98=", - "aarch64-linux": "sha256-c254hgVVyLFucacOQlpnJ+3eCw8xIK9+388EigiuKeM=", - "aarch64-darwin": "sha256-A71PII7Ue0uqsH970GPi0XfRKInpSFFom8jD3Q0wrgQ=", - "x86_64-darwin": "sha256-M9q8O0GkiNoDPMW4C3EVvLo+X9mjQYLJTtNZx5AuQCc=" + "x86_64-linux": "sha256-ZH0Rwfh8Sqqcs0dymB8KYqQp3cwiCOHHKMjChm8lyOI=", + "aarch64-linux": "sha256-P8dIIFovBuGvfTKLOPQQbblF7jLpuZTWYkOMyuO3V00=", + "aarch64-darwin": "sha256-EaiJGw0AtlqlDCXo8ygA37asotkxEYXeiMj+wpJb2aU=", + "x86_64-darwin": "sha256-x0dbpIMN3yaJjCpKoK3SmpO24DkTJqUJ+52hBRDXLW4=" } } From b1c44c7e5c70d567404ee0ee3111cd74871c7f20 Mon Sep 17 00:00:00 2001 From: Daniel Polito Date: Thu, 5 Feb 2026 16:38:31 -0300 Subject: [PATCH 43/59] feat(desktop): Set Workspace Name Earlier to Improve Creation / Deletion (#12213) --- packages/app/src/pages/layout.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 903b9eaa1f..f7771ba76e 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -2574,6 +2574,8 @@ export default function Layout(props: ParentProps) { if (!created?.directory) return + setWorkspaceName(created.directory, created.branch, project.id, created.branch) + const local = project.worktree const key = workspaceKey(created.directory) const root = workspaceKey(local) From c40ce47e92befbe4cb27735e4d870f540e75b646 Mon Sep 17 00:00:00 2001 From: Daniel Polito Date: Thu, 5 Feb 2026 16:46:44 -0300 Subject: [PATCH 44/59] feat(desktop): Stop Showing SessionSkeleton on New Workspace (#12209) --- packages/app/src/pages/layout.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index f7771ba76e..251984d776 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -2055,9 +2055,10 @@ export default function Layout(props: ParentProps) { const open = createMemo(() => store.workspaceExpanded[props.directory] ?? local()) const boot = createMemo(() => open() || active()) const booted = createMemo((prev) => prev || workspaceStore.status === "complete", false) - const loading = createMemo(() => open() && !booted() && sessions().length === 0) const hasMore = createMemo(() => workspaceStore.sessionTotal > sessions().length) const busy = createMemo(() => isBusy(props.directory)) + const wasBusy = createMemo((prev) => prev || busy(), false) + const loading = createMemo(() => open() && !booted() && sessions().length === 0 && !wasBusy()) const loadMore = async () => { setWorkspaceStore("limit", (limit) => limit + 5) await globalSync.project.loadSessions(props.directory) From 83646e0366c47a3bccb5135d40628176a6776f33 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:51:08 -0600 Subject: [PATCH 45/59] fix(app): allow toggling file tree closed independently (#12293) --- .../app/src/components/dialog-select-file.tsx | 2 + packages/app/src/components/prompt-input.tsx | 3 + .../src/components/session-context-usage.tsx | 2 + .../src/components/session/session-header.tsx | 46 +++++++-- packages/app/src/context/layout.tsx | 62 ++++++++++++- packages/app/src/pages/session.tsx | 93 +++++++++++++++++-- 6 files changed, 185 insertions(+), 23 deletions(-) diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index 167f211953..36448dd3e6 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -47,6 +47,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil const filesOnly = () => props.mode === "files" const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) const tabs = createMemo(() => layout.tabs(sessionKey)) + const view = createMemo(() => layout.view(sessionKey)) const state = { cleanup: undefined as (() => void) | void, committed: false } const [grouped, setGrouped] = createSignal(false) const common = [ @@ -282,6 +283,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil const value = file.tab(path) tabs().open(value) file.load(path) + if (!view().reviewPanel.opened()) view().reviewPanel.open() layout.fileTree.open() layout.fileTree.setTab("all") props.onOpenFile?.(path) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index b897e394aa..f40b61bca5 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -172,6 +172,7 @@ export const PromptInput: Component = (props) => { const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) const tabs = createMemo(() => layout.tabs(sessionKey)) + const view = createMemo(() => layout.view(sessionKey)) const commentInReview = (path: string) => { const sessionID = params.id @@ -190,12 +191,14 @@ export const PromptInput: Component = (props) => { const wantsReview = item.commentOrigin === "review" || (item.commentOrigin !== "file" && commentInReview(item.path)) if (wantsReview) { + if (!view().reviewPanel.opened()) view().reviewPanel.open() layout.fileTree.open() layout.fileTree.setTab("changes") requestAnimationFrame(() => comments.setFocus(focus)) return } + if (!view().reviewPanel.opened()) view().reviewPanel.open() layout.fileTree.open() layout.fileTree.setTab("all") const tab = files.tab(item.path) diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index c5de54cf0f..c6256395fc 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -23,6 +23,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) { const variant = createMemo(() => props.variant ?? "button") const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) const tabs = createMemo(() => layout.tabs(sessionKey)) + const view = createMemo(() => layout.view(sessionKey)) const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : [])) const usd = createMemo( @@ -57,6 +58,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) { const openContext = () => { if (!params.id) return + if (!view().reviewPanel.opened()) view().reviewPanel.open() layout.fileTree.open() layout.fileTree.setTab("all") tabs().open("context") diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 5b00f80c05..f2bfc8d251 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -283,27 +283,57 @@ export function SessionHeader() { + +
+
- + {/* Desktop side panel - hidden on mobile */} - +