From e5687d646ce33b5c05bb007bf14cf5362676733b Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:36:56 +0800 Subject: [PATCH 001/426] electron: use custom oc:// protocol for renderer windows (#23516) --- packages/desktop-electron/src/main/index.ts | 3 +- packages/desktop-electron/src/main/server.ts | 1 + packages/desktop-electron/src/main/windows.ts | 41 +++++++++++++++++-- .../src/renderer/html.test.ts | 6 +-- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/packages/desktop-electron/src/main/index.ts b/packages/desktop-electron/src/main/index.ts index 6c4e6d5ca..8ec39c3c9 100644 --- a/packages/desktop-electron/src/main/index.ts +++ b/packages/desktop-electron/src/main/index.ts @@ -42,7 +42,7 @@ import { initLogging } from "./logging" import { parseMarkdown } from "./markdown" import { createMenu } from "./menu" import { getDefaultServerUrl, getWslConfig, setDefaultServerUrl, setWslConfig, spawnLocalServer } from "./server" -import { createLoadingWindow, createMainWindow, setBackgroundColor, setDockIcon } from "./windows" +import { createLoadingWindow, createMainWindow, registerRendererProtocol, setBackgroundColor, setDockIcon } from "./windows" import { drizzle } from "drizzle-orm/node-sqlite/driver" import type { Server } from "virtual:opencode-server" @@ -106,6 +106,7 @@ function setupApp() { void app.whenReady().then(async () => { app.setAsDefaultProtocolClient("opencode") + registerRendererProtocol() setDockIcon() setupAutoUpdater() await initialize() diff --git a/packages/desktop-electron/src/main/server.ts b/packages/desktop-electron/src/main/server.ts index 55dfdf6e9..83d50f7cb 100644 --- a/packages/desktop-electron/src/main/server.ts +++ b/packages/desktop-electron/src/main/server.ts @@ -39,6 +39,7 @@ export async function spawnLocalServer(hostname: string, port: number, password: hostname, username: "opencode", password, + cors: ["oc://renderer"], }) const wait = (async () => { diff --git a/packages/desktop-electron/src/main/windows.ts b/packages/desktop-electron/src/main/windows.ts index 95f80c124..892e9d40d 100644 --- a/packages/desktop-electron/src/main/windows.ts +++ b/packages/desktop-electron/src/main/windows.ts @@ -1,7 +1,7 @@ import windowState from "electron-window-state" -import { app, BrowserWindow, nativeImage, nativeTheme } from "electron" -import { dirname, join } from "node:path" -import { fileURLToPath } from "node:url" +import { app, BrowserWindow, net, nativeImage, nativeTheme, protocol } from "electron" +import { dirname, isAbsolute, join, relative, resolve } from "node:path" +import { fileURLToPath, pathToFileURL } from "node:url" import type { TitlebarTheme } from "../preload/types" type Globals = { @@ -10,6 +10,20 @@ type Globals = { } const root = dirname(fileURLToPath(import.meta.url)) +const rendererRoot = join(root, "../renderer") +const rendererProtocol = "oc" +const rendererHost = "renderer" + +protocol.registerSchemesAsPrivileged([ + { + scheme: rendererProtocol, + privileges: { + secure: true, + standard: true, + supportFetchAPI: true, + }, + }, +]) let backgroundColor: string | undefined @@ -131,6 +145,25 @@ export function createLoadingWindow(globals: Globals) { return win } +export function registerRendererProtocol() { + if (protocol.isProtocolHandled(rendererProtocol)) return + + protocol.handle(rendererProtocol, (request) => { + const url = new URL(request.url) + if (url.host !== rendererHost) { + return new Response("Not found", { status: 404 }) + } + + const file = resolve(rendererRoot, `.${decodeURIComponent(url.pathname)}`) + const rel = relative(rendererRoot, file) + if (rel.startsWith("..") || isAbsolute(rel)) { + return new Response("Not found", { status: 404 }) + } + + return net.fetch(pathToFileURL(file).toString()) + }) +} + function loadWindow(win: BrowserWindow, html: string) { const devUrl = process.env.ELECTRON_RENDERER_URL if (devUrl) { @@ -139,7 +172,7 @@ function loadWindow(win: BrowserWindow, html: string) { return } - void win.loadFile(join(root, `../renderer/${html}`)) + void win.loadURL(`${rendererProtocol}://${rendererHost}/${html}`) } function injectGlobals(win: BrowserWindow, globals: Globals) { diff --git a/packages/desktop-electron/src/renderer/html.test.ts b/packages/desktop-electron/src/renderer/html.test.ts index bd8281c2f..1fc5c8717 100644 --- a/packages/desktop-electron/src/renderer/html.test.ts +++ b/packages/desktop-electron/src/renderer/html.test.ts @@ -9,9 +9,9 @@ const root = resolve(dir, "../..") const html = async (name: string) => Bun.file(join(dir, name)).text() /** - * Electron loads renderer HTML via `win.loadFile()` which uses the `file://` - * protocol. Absolute paths like `src="/foo.js"` resolve to the filesystem root - * (e.g. `file:///C:/foo.js` on Windows) instead of relative to the app bundle. + * Packaged Electron windows load renderer HTML via the privileged `oc://` + * protocol. Root-relative asset paths like `src="/foo.js"` would resolve from + * the protocol origin root instead of relative to the current HTML entrypoint. * * All local resource references must use relative paths (`./`). */ From 4964ce480c566a98b2b4ead4a6e163eb773c2b80 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 21 Apr 2026 04:37:57 +0000 Subject: [PATCH 002/426] chore: generate --- packages/desktop-electron/src/main/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/desktop-electron/src/main/index.ts b/packages/desktop-electron/src/main/index.ts index 8ec39c3c9..8a826bd27 100644 --- a/packages/desktop-electron/src/main/index.ts +++ b/packages/desktop-electron/src/main/index.ts @@ -42,7 +42,13 @@ import { initLogging } from "./logging" import { parseMarkdown } from "./markdown" import { createMenu } from "./menu" import { getDefaultServerUrl, getWslConfig, setDefaultServerUrl, setWslConfig, spawnLocalServer } from "./server" -import { createLoadingWindow, createMainWindow, registerRendererProtocol, setBackgroundColor, setDockIcon } from "./windows" +import { + createLoadingWindow, + createMainWindow, + registerRendererProtocol, + setBackgroundColor, + setDockIcon, +} from "./windows" import { drizzle } from "drizzle-orm/node-sqlite/driver" import type { Server } from "virtual:opencode-server" From eb9906420fa8def2520b1b4950a9175af9116ea2 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:38:59 +0800 Subject: [PATCH 003/426] refactor(desktop-electron): enable contextIsolation and sandbox (#23523) --- .../desktop-electron/electron.vite.config.ts | 4 ++ packages/desktop-electron/src/main/index.ts | 11 ++---- packages/desktop-electron/src/main/ipc.ts | 13 ++++++- packages/desktop-electron/src/main/menu.ts | 2 +- packages/desktop-electron/src/main/windows.ts | 37 +++++-------------- .../desktop-electron/src/preload/index.ts | 2 + .../desktop-electron/src/preload/types.ts | 6 +++ .../desktop-electron/src/renderer/env.d.ts | 2 - .../desktop-electron/src/renderer/index.tsx | 30 ++++++++------- .../desktop-electron/src/renderer/updater.ts | 2 - 10 files changed, 55 insertions(+), 54 deletions(-) diff --git a/packages/desktop-electron/electron.vite.config.ts b/packages/desktop-electron/electron.vite.config.ts index d0e6c42b6..f28c7b6c1 100644 --- a/packages/desktop-electron/electron.vite.config.ts +++ b/packages/desktop-electron/electron.vite.config.ts @@ -53,6 +53,10 @@ export default defineConfig({ build: { rollupOptions: { input: { index: "src/preload/index.ts" }, + output: { + format: "cjs", + entryFileNames: "[name].js", + }, }, }, }, diff --git a/packages/desktop-electron/src/main/index.ts b/packages/desktop-electron/src/main/index.ts index 8a826bd27..ae9f58118 100644 --- a/packages/desktop-electron/src/main/index.ts +++ b/packages/desktop-electron/src/main/index.ts @@ -195,15 +195,10 @@ async function initialize() { logger.log("loading task finished") })() - const globals = { - updaterEnabled: UPDATER_ENABLED, - deepLinks: pendingDeepLinks, - } - if (needsMigration) { const show = await Promise.race([loadingTask.then(() => false), delay(1_000).then(() => true)]) if (show) { - overlay = createLoadingWindow(globals) + overlay = createLoadingWindow() await delay(1_000) } } @@ -215,7 +210,7 @@ async function initialize() { await loadingComplete.promise } - mainWindow = createMainWindow(globals) + mainWindow = createMainWindow() wireMenu() overlay?.close() @@ -252,6 +247,8 @@ registerIpcHandlers({ initEmitter.off("step", listener) } }, + getWindowConfig: () => ({ updaterEnabled: UPDATER_ENABLED }), + consumeInitialDeepLinks: () => pendingDeepLinks.splice(0), getDefaultServerUrl: () => getDefaultServerUrl(), setDefaultServerUrl: (url) => setDefaultServerUrl(url), getWslConfig: () => Promise.resolve(getWslConfig()), diff --git a/packages/desktop-electron/src/main/ipc.ts b/packages/desktop-electron/src/main/ipc.ts index 52d87ed7e..8dbca8eea 100644 --- a/packages/desktop-electron/src/main/ipc.ts +++ b/packages/desktop-electron/src/main/ipc.ts @@ -2,7 +2,14 @@ import { execFile } from "node:child_process" import { BrowserWindow, Notification, app, clipboard, dialog, ipcMain, shell } from "electron" import type { IpcMainEvent, IpcMainInvokeEvent } from "electron" -import type { InitStep, ServerReadyData, SqliteMigrationProgress, TitlebarTheme, WslConfig } from "../preload/types" +import type { + InitStep, + ServerReadyData, + SqliteMigrationProgress, + TitlebarTheme, + WindowConfig, + WslConfig, +} from "../preload/types" import { getStore } from "./store" import { setTitlebar } from "./windows" @@ -14,6 +21,8 @@ const pickerFilters = (ext?: string[]) => { type Deps = { killSidecar: () => void awaitInitialization: (sendStep: (step: InitStep) => void) => Promise + getWindowConfig: () => Promise | WindowConfig + consumeInitialDeepLinks: () => Promise | string[] getDefaultServerUrl: () => Promise | string | null setDefaultServerUrl: (url: string | null) => Promise | void getWslConfig: () => Promise @@ -37,6 +46,8 @@ export function registerIpcHandlers(deps: Deps) { const send = (step: InitStep) => event.sender.send("init-step", step) return deps.awaitInitialization(send) }) + ipcMain.handle("get-window-config", () => deps.getWindowConfig()) + ipcMain.handle("consume-initial-deep-links", () => deps.consumeInitialDeepLinks()) ipcMain.handle("get-default-server-url", () => deps.getDefaultServerUrl()) ipcMain.handle("set-default-server-url", (_event: IpcMainInvokeEvent, url: string | null) => deps.setDefaultServerUrl(url), diff --git a/packages/desktop-electron/src/main/menu.ts b/packages/desktop-electron/src/main/menu.ts index fcf209fb6..0d9a697fa 100644 --- a/packages/desktop-electron/src/main/menu.ts +++ b/packages/desktop-electron/src/main/menu.ts @@ -47,7 +47,7 @@ export function createMenu(deps: Deps) { { label: "New Window", accelerator: "Cmd+Shift+N", - click: () => createMainWindow({ updaterEnabled: UPDATER_ENABLED }), + click: () => createMainWindow(), }, { type: "separator" }, { role: "close" }, diff --git a/packages/desktop-electron/src/main/windows.ts b/packages/desktop-electron/src/main/windows.ts index 892e9d40d..df55e8da2 100644 --- a/packages/desktop-electron/src/main/windows.ts +++ b/packages/desktop-electron/src/main/windows.ts @@ -4,11 +4,6 @@ import { dirname, isAbsolute, join, relative, resolve } from "node:path" import { fileURLToPath, pathToFileURL } from "node:url" import type { TitlebarTheme } from "../preload/types" -type Globals = { - updaterEnabled: boolean - deepLinks?: string[] -} - const root = dirname(fileURLToPath(import.meta.url)) const rendererRoot = join(root, "../renderer") const rendererProtocol = "oc" @@ -68,7 +63,7 @@ export function setDockIcon() { if (!icon.isEmpty()) app.dock?.setIcon(icon) } -export function createMainWindow(globals: Globals) { +export function createMainWindow() { const state = windowState({ defaultWidth: 1280, defaultHeight: 800, @@ -98,15 +93,16 @@ export function createMainWindow(globals: Globals) { } : {}), webPreferences: { - preload: join(root, "../preload/index.mjs"), - sandbox: false, + preload: join(root, "../preload/index.js"), + contextIsolation: true, + nodeIntegration: false, + sandbox: true, }, }) state.manage(win) loadWindow(win, "index.html") wireZoom(win) - injectGlobals(win, globals) win.once("ready-to-show", () => { win.show() @@ -115,7 +111,7 @@ export function createMainWindow(globals: Globals) { return win } -export function createLoadingWindow(globals: Globals) { +export function createLoadingWindow() { const mode = tone() const win = new BrowserWindow({ width: 640, @@ -134,13 +130,14 @@ export function createLoadingWindow(globals: Globals) { } : {}), webPreferences: { - preload: join(root, "../preload/index.mjs"), - sandbox: false, + preload: join(root, "../preload/index.js"), + contextIsolation: true, + nodeIntegration: false, + sandbox: true, }, }) loadWindow(win, "loading.html") - injectGlobals(win, globals) return win } @@ -174,20 +171,6 @@ function loadWindow(win: BrowserWindow, html: string) { void win.loadURL(`${rendererProtocol}://${rendererHost}/${html}`) } - -function injectGlobals(win: BrowserWindow, globals: Globals) { - win.webContents.on("dom-ready", () => { - const deepLinks = globals.deepLinks ?? [] - const data = { - updaterEnabled: globals.updaterEnabled, - deepLinks: Array.isArray(deepLinks) ? deepLinks.splice(0) : deepLinks, - } - void win.webContents.executeJavaScript( - `window.__OPENCODE__ = Object.assign(window.__OPENCODE__ ?? {}, ${JSON.stringify(data)})`, - ) - }) -} - function wireZoom(win: BrowserWindow) { win.webContents.setZoomFactor(1) win.webContents.on("zoom-changed", () => { diff --git a/packages/desktop-electron/src/preload/index.ts b/packages/desktop-electron/src/preload/index.ts index 296fcb2f1..6261419ca 100644 --- a/packages/desktop-electron/src/preload/index.ts +++ b/packages/desktop-electron/src/preload/index.ts @@ -11,6 +11,8 @@ const api: ElectronAPI = { ipcRenderer.removeListener("init-step", handler) }) }, + getWindowConfig: () => ipcRenderer.invoke("get-window-config"), + consumeInitialDeepLinks: () => ipcRenderer.invoke("consume-initial-deep-links"), getDefaultServerUrl: () => ipcRenderer.invoke("get-default-server-url"), setDefaultServerUrl: (url) => ipcRenderer.invoke("set-default-server-url", url), getWslConfig: () => ipcRenderer.invoke("get-wsl-config"), diff --git a/packages/desktop-electron/src/preload/types.ts b/packages/desktop-electron/src/preload/types.ts index f8e6d52c7..6e22954d1 100644 --- a/packages/desktop-electron/src/preload/types.ts +++ b/packages/desktop-electron/src/preload/types.ts @@ -15,10 +15,16 @@ export type TitlebarTheme = { mode: "light" | "dark" } +export type WindowConfig = { + updaterEnabled: boolean +} + export type ElectronAPI = { killSidecar: () => Promise installCli: () => Promise awaitInitialization: (onStep: (step: InitStep) => void) => Promise + getWindowConfig: () => Promise + consumeInitialDeepLinks: () => Promise getDefaultServerUrl: () => Promise setDefaultServerUrl: (url: string | null) => Promise getWslConfig: () => Promise diff --git a/packages/desktop-electron/src/renderer/env.d.ts b/packages/desktop-electron/src/renderer/env.d.ts index d1590ff04..6dff3baf1 100644 --- a/packages/desktop-electron/src/renderer/env.d.ts +++ b/packages/desktop-electron/src/renderer/env.d.ts @@ -4,8 +4,6 @@ declare global { interface Window { api: ElectronAPI __OPENCODE__?: { - updaterEnabled?: boolean - wsl?: boolean deepLinks?: string[] } } diff --git a/packages/desktop-electron/src/renderer/index.tsx b/packages/desktop-electron/src/renderer/index.tsx index 44f2e6360..843863290 100644 --- a/packages/desktop-electron/src/renderer/index.tsx +++ b/packages/desktop-electron/src/renderer/index.tsx @@ -20,7 +20,6 @@ import { createEffect, createResource, onCleanup, onMount, Show } from "solid-js import { render } from "solid-js/web" import pkg from "../../package.json" import { initI18n, t } from "./i18n" -import { UPDATER_ENABLED } from "./updater" import { webviewZoom } from "./webview-zoom" import "./styles.css" import { useTheme } from "@opencode-ai/ui/theme" @@ -43,8 +42,7 @@ const emitDeepLinks = (urls: string[]) => { } const listenForDeepLinks = () => { - const startUrls = window.__OPENCODE__?.deepLinks ?? [] - if (startUrls.length) emitDeepLinks(startUrls) + void window.api.consumeInitialDeepLinks().then((urls) => emitDeepLinks(urls)) return window.api.onDeepLink((urls) => emitDeepLinks(urls)) } @@ -57,13 +55,18 @@ const createPlatform = (): Platform => { return undefined })() + const isWslEnabled = async () => { + if (os !== "windows") return false + return window.api.getWslConfig().then((config) => config.enabled).catch(() => false) + } + const wslHome = async () => { - if (os !== "windows" || !window.__OPENCODE__?.wsl) return undefined + if (!(await isWslEnabled())) return undefined return window.api.wslPath("~", "windows").catch(() => undefined) } const handleWslPicker = async (result: T | null): Promise => { - if (!result || !window.__OPENCODE__?.wsl) return result + if (!result || !(await isWslEnabled())) return result if (Array.isArray(result)) { return Promise.all(result.map((path) => window.api.wslPath(path, "linux").catch(() => path))) as any } @@ -137,7 +140,7 @@ const createPlatform = (): Platform => { if (os === "windows") { const resolvedApp = app ? await window.api.resolveAppPath(app).catch(() => null) : null const resolvedPath = await (async () => { - if (window.__OPENCODE__?.wsl) { + if (await isWslEnabled()) { const converted = await window.api.wslPath(path, "windows").catch(() => null) if (converted) return converted } @@ -159,12 +162,14 @@ const createPlatform = (): Platform => { storage, checkUpdate: async () => { - if (!UPDATER_ENABLED()) return { updateAvailable: false } + const config = await window.api.getWindowConfig().catch(() => ({ updaterEnabled: false })) + if (!config.updaterEnabled) return { updateAvailable: false } return window.api.checkUpdate() }, update: async () => { - if (!UPDATER_ENABLED()) return + const config = await window.api.getWindowConfig().catch(() => ({ updaterEnabled: false })) + if (!config.updaterEnabled) return await window.api.installUpdate() }, @@ -194,11 +199,7 @@ const createPlatform = (): Platform => { return fetch(input, init) }, - getWslEnabled: async () => { - const next = await window.api.getWslConfig().catch(() => null) - if (next) return next.enabled - return window.__OPENCODE__!.wsl ?? false - }, + getWslEnabled: () => isWslEnabled(), setWslEnabled: async (enabled) => { await window.api.setWslConfig({ enabled }) @@ -249,6 +250,7 @@ listenForDeepLinks() render(() => { const platform = createPlatform() + const [windowConfig] = createResource(() => window.api.getWindowConfig().catch(() => ({ updaterEnabled: false }))) const loadLocale = async () => { const current = await platform.storage?.("opencode.global.dat").getItem("language") const legacy = current ? undefined : await platform.storage?.().getItem("language.v1") @@ -325,7 +327,7 @@ render(() => { return ( - + {(_) => { return ( window.__OPENCODE__?.updaterEnabled ?? false - export async function runUpdater({ alertOnFail }: { alertOnFail: boolean }) { await initI18n() try { From a08aa21cb35d3c112d93af5abf39237a187265d6 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 21 Apr 2026 04:40:02 +0000 Subject: [PATCH 004/426] chore: generate --- packages/desktop-electron/src/renderer/index.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/desktop-electron/src/renderer/index.tsx b/packages/desktop-electron/src/renderer/index.tsx index 843863290..56fe9fa51 100644 --- a/packages/desktop-electron/src/renderer/index.tsx +++ b/packages/desktop-electron/src/renderer/index.tsx @@ -57,7 +57,10 @@ const createPlatform = (): Platform => { const isWslEnabled = async () => { if (os !== "windows") return false - return window.api.getWslConfig().then((config) => config.enabled).catch(() => false) + return window.api + .getWslConfig() + .then((config) => config.enabled) + .catch(() => false) } const wslHome = async () => { @@ -327,7 +330,15 @@ render(() => { return ( - + {(_) => { return ( Date: Tue, 21 Apr 2026 01:15:07 -0400 Subject: [PATCH 005/426] zen: m2.7 & k2.6 --- packages/web/src/content/docs/ar/zen.mdx | 15 ++++----------- packages/web/src/content/docs/bs/zen.mdx | 15 ++++----------- packages/web/src/content/docs/da/zen.mdx | 15 ++++----------- packages/web/src/content/docs/de/zen.mdx | 15 ++++----------- packages/web/src/content/docs/es/zen.mdx | 15 ++++----------- packages/web/src/content/docs/fr/zen.mdx | 15 ++++----------- packages/web/src/content/docs/it/zen.mdx | 15 ++++----------- packages/web/src/content/docs/ja/zen.mdx | 15 ++++----------- packages/web/src/content/docs/ko/zen.mdx | 15 ++++----------- packages/web/src/content/docs/nb/zen.mdx | 15 ++++----------- packages/web/src/content/docs/pl/zen.mdx | 15 ++++----------- packages/web/src/content/docs/pt-br/zen.mdx | 15 ++++----------- packages/web/src/content/docs/ru/zen.mdx | 15 ++++----------- packages/web/src/content/docs/th/zen.mdx | 15 ++++----------- packages/web/src/content/docs/tr/zen.mdx | 15 ++++----------- packages/web/src/content/docs/zen.mdx | 2 ++ packages/web/src/content/docs/zh-cn/zen.mdx | 15 ++++----------- packages/web/src/content/docs/zh-tw/zen.mdx | 15 ++++----------- 18 files changed, 70 insertions(+), 187 deletions(-) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index 60225a7ea..6150c7295 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -89,13 +89,12 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | يستخدم [معرّف النموذج](/docs/config/#models) في إعدادات OpenCode الصيغة `opencode/`. على سبيل المثال، بالنسبة إلى GPT 5.3 Codex، ستستخدم `opencode/gpt-5.3-codex` في إعداداتك. @@ -119,18 +118,16 @@ https://opencode.ai/zen/v1/models | النموذج | الإدخال | الإخراج | القراءة المخزنة | الكتابة المخزنة | | --------------------------------- | ------- | ------- | --------------- | --------------- | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -170,8 +167,6 @@ https://opencode.ai/zen/v1/models النماذج المجانية: - MiniMax M2.5 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. -- MiMo V2 Pro Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. -- MiMo V2 Omni Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Nemotron 3 Super Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Big Pickle نموذج خفي ومتاح مجانا على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. @@ -215,8 +210,6 @@ https://opencode.ai/zen/v1/models - Big Pickle: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - MiniMax M2.5 Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. -- MiMo V2 Pro Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. -- MiMo V2 Omni Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - Nemotron 3 Super Free (نقاط نهاية NVIDIA المجانية): يُقدَّم بموجب [شروط خدمة النسخة التجريبية من واجهة NVIDIA API](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). للاستخدام التجريبي فقط، وليس للإنتاج أو البيانات الحساسة. تقوم NVIDIA بتسجيل المطالبات والمخرجات لتحسين نماذجها وخدماتها. لا ترسل بيانات شخصية أو سرية. - OpenAI APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index 18d6ae9f0..70ac7641f 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -94,13 +94,12 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) u vašoj OpenCode konfiguraciji koristi format @@ -126,18 +125,16 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -177,8 +174,6 @@ Naknade za kreditne kartice prosljeđujemo po stvarnom trošku (4.4% + $0.30 po Besplatni modeli: - MiniMax M2.5 Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. -- MiMo V2 Pro Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. -- MiMo V2 Omni Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Nemotron 3 Super Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Big Pickle je stealth model koji je besplatan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. @@ -227,8 +222,6 @@ i ne koriste vaše podatke za treniranje modela, uz sljedeće izuzetke: - Big Pickle: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - MiniMax M2.5 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. -- MiMo V2 Pro Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. -- MiMo V2 Omni Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - Nemotron 3 Super Free (besplatni NVIDIA endpointi): Dostupan je prema [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Samo za probnu upotrebu, nije za produkciju niti osjetljive podatke. NVIDIA bilježi promptove i izlaze radi poboljšanja svojih modela i usluga. Nemojte slati lične ili povjerljive podatke. - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 71242882c..c497f35b7 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -94,13 +94,12 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) i din OpenCode-konfiguration @@ -126,18 +125,16 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -177,8 +174,6 @@ Kreditkortgebyrer videregives til kostpris (4.4% + $0.30 pr. transaktion); vi op De gratis modeller: - MiniMax M2.5 Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. -- MiMo V2 Pro Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. -- MiMo V2 Omni Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Nemotron 3 Super Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Big Pickle er en stealth-model, som er gratis på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. @@ -225,8 +220,6 @@ Alle vores modeller hostes i US. Vores udbydere følger en nul-opbevaringspoliti - Big Pickle: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - MiniMax M2.5 Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. -- MiMo V2 Pro Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. -- MiMo V2 Omni Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - Nemotron 3 Super Free (gratis NVIDIA-endpoints): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun til prøvebrug, ikke til produktion eller følsomme data. Prompts og outputs logges af NVIDIA for at forbedre deres modeller og tjenester. Indsend ikke personlige eller fortrolige data. - OpenAI APIs: Anmodninger opbevares i 30 dage i overensstemmelse med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Anmodninger opbevares i 30 dage i overensstemmelse med [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index b5319c1ef..0dfc72850 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -85,13 +85,12 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | Die [Model-ID](/docs/config/#models) in deiner OpenCode-Konfiguration verwendet das Format `opencode/`. Für GPT 5.3 Codex würdest du zum Beispiel `opencode/gpt-5.3-codex` in deiner Konfiguration verwenden. @@ -115,18 +114,16 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -166,8 +163,6 @@ Kreditkartengebühren werden zum Selbstkostenpreis weitergegeben (4.4% + $0.30 p Die kostenlosen Modelle: - MiniMax M2.5 Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. -- MiMo V2 Pro Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. -- MiMo V2 Omni Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Nemotron 3 Super Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Big Pickle ist ein Stealth-Modell, das für begrenzte Zeit kostenlos auf OpenCode verfügbar ist. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. @@ -211,8 +206,6 @@ Alle unsere Modelle werden in den USA gehostet. Unsere Provider folgen einer Zer - Big Pickle: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - MiniMax M2.5 Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. -- MiMo V2 Pro Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. -- MiMo V2 Omni Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - Nemotron 3 Super Free (kostenlose NVIDIA-Endpunkte): Bereitgestellt gemäß den [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Nur für Testzwecke, nicht für Produktion oder sensible Daten. Eingaben und Ausgaben werden von NVIDIA protokolliert, um seine Modelle und Dienste zu verbessern. Übermitteln Sie keine personenbezogenen oder vertraulichen Daten. - OpenAI APIs: Anfragen werden in Übereinstimmung mit [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 30 Tage lang gespeichert. - Anthropic APIs: Anfragen werden in Übereinstimmung mit [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 30 Tage lang gespeichert. diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 3ac71297c..4a2866aa7 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -94,13 +94,12 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | El [identificador del modelo](/docs/config/#models) en tu configuración de OpenCode @@ -126,18 +125,16 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Modelo | Entrada | Salida | Lectura en caché | Escritura en caché | | --------------------------------- | ------- | ------- | ---------------- | ------------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -177,8 +174,6 @@ Las comisiones de tarjeta de crédito se trasladan al costo (4.4% + $0.30 por tr Los modelos gratuitos: - MiniMax M2.5 Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. -- MiMo V2 Pro Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. -- MiMo V2 Omni Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Nemotron 3 Super Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Big Pickle es un modelo stealth que es gratuito en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. @@ -225,8 +220,6 @@ Todos nuestros modelos están alojados en US. Nuestros proveedores siguen una po - Big Pickle: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - MiniMax M2.5 Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. -- MiMo V2 Pro Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. -- MiMo V2 Omni Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - Nemotron 3 Super Free (endpoints gratuitos de NVIDIA): Se ofrece bajo los [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo para uso de prueba, no para producción ni datos sensibles. NVIDIA registra los prompts y las salidas para mejorar sus modelos y servicios. No envíes datos personales ni confidenciales. - OpenAI APIs: Las solicitudes se conservan durante 30 días de acuerdo con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Las solicitudes se conservan durante 30 días de acuerdo con [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index 3ac75d895..247ae00d2 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -85,13 +85,12 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | Le [model id](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode/`. Par exemple, pour GPT 5.3 Codex, vous utiliseriez `opencode/gpt-5.3-codex` dans votre configuration. @@ -115,18 +114,16 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Modèle | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -166,8 +163,6 @@ Les frais de carte de crédit sont répercutés au prix coûtant (4.4% + $0.30 p Les modèles gratuits : - MiniMax M2.5 Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. -- MiMo V2 Pro Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. -- MiMo V2 Omni Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Nemotron 3 Super Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Big Pickle est un modèle stealth gratuit sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. @@ -211,8 +206,6 @@ Tous nos modèles sont hébergés aux US. Nos fournisseurs suivent une politique - Big Pickle : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - MiniMax M2.5 Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. -- MiMo V2 Pro Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. -- MiMo V2 Omni Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - Nemotron 3 Super Free (endpoints NVIDIA gratuits) : Fourni dans le cadre des [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Réservé à un usage d'essai, pas à la production ni aux données sensibles. Les prompts et les sorties sont journalisés par NVIDIA pour améliorer ses modèles et services. N'envoyez pas de données personnelles ou confidentielles. - OpenAI APIs : Les requêtes sont conservées pendant 30 jours conformément à [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs : Les requêtes sont conservées pendant 30 jours conformément à [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 927c5b441..0922c51a5 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -94,13 +94,12 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | Il [model id](/docs/config/#models) nella config di OpenCode @@ -126,18 +125,16 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Modello | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -177,8 +174,6 @@ Le commissioni della carta di credito vengono trasferite al costo (4.4% + $0.30 I modelli gratuiti: - MiniMax M2.5 Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. -- MiMo V2 Pro Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. -- MiMo V2 Omni Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Nemotron 3 Super Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Big Pickle è un modello stealth che è gratuito su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. @@ -225,8 +220,6 @@ Tutti i nostri modelli sono ospitati negli US. I nostri provider seguono una pol - Big Pickle: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - MiniMax M2.5 Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. -- MiMo V2 Pro Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. -- MiMo V2 Omni Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - Nemotron 3 Super Free (endpoint NVIDIA gratuiti): fornito secondo i [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo per uso di prova, non per produzione o dati sensibili. NVIDIA registra prompt e output per migliorare i propri modelli e servizi. Non inviare dati personali o riservati. - OpenAI APIs: le richieste vengono conservate per 30 giorni in conformità con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: le richieste vengono conservate per 30 giorni in conformità con [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index 772bb8d43..7419bd4c4 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -85,13 +85,12 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode 設定で使う [model id](/docs/config/#models) は `opencode/` 形式です。たとえば、GPT 5.3 Codex では設定に `opencode/gpt-5.3-codex` を使用します。 @@ -115,18 +114,16 @@ https://opencode.ai/zen/v1/models | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -166,8 +163,6 @@ https://opencode.ai/zen/v1/models 無料モデル: - MiniMax M2.5 Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 -- MiMo V2 Pro Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 -- MiMo V2 Omni Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Nemotron 3 Super Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Big Pickle はステルスモデルで、期間限定で OpenCode で無料提供されています。チームはこの期間中にフィードバックを集め、モデルを改善しています。 @@ -211,8 +206,6 @@ https://opencode.ai/zen/v1/models - Big Pickle: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - MiniMax M2.5 Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 -- MiMo V2 Pro Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 -- MiMo V2 Omni Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - Nemotron 3 Super Free(NVIDIA の無料エンドポイント): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) に基づいて提供されます。試用専用であり、本番環境や機密性の高いデータには使用しないでください。プロンプトと出力は、NVIDIA が自社のモデルとサービスを改善するために記録します。個人情報や機密データは送信しないでください。 - OpenAI APIs: リクエストは [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) に従って 30 日間保持されます。 - Anthropic APIs: リクエストは [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) に従って 30 日間保持されます。 diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 143906aea..3d796ee99 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -85,13 +85,12 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode config에서 사용하는 [모델 ID](/docs/config/#models)는 `opencode/` 형식입니다. 예를 들어 GPT 5.3 Codex를 사용하려면 config에서 `opencode/gpt-5.3-codex`를 사용하면 됩니다. @@ -115,18 +114,16 @@ https://opencode.ai/zen/v1/models | 모델 | 입력 | 출력 | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -166,8 +163,6 @@ https://opencode.ai/zen/v1/models 무료 모델: - MiniMax M2.5 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. -- MiMo V2 Pro Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. -- MiMo V2 Omni Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Nemotron 3 Super Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Big Pickle은 한정된 기간 동안 OpenCode에서 무료로 제공되는 stealth model입니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. @@ -211,8 +206,6 @@ https://opencode.ai/zen/v1/models - Big Pickle: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - MiniMax M2.5 Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. -- MiMo V2 Pro Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. -- MiMo V2 Omni Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - Nemotron 3 Super Free(NVIDIA 무료 엔드포인트): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)에 따라 제공됩니다. 평가판 전용이며 프로덕션 환경이나 민감한 데이터에는 사용할 수 없습니다. NVIDIA는 자사 모델과 서비스를 개선하기 위해 프롬프트와 출력을 기록합니다. 개인 정보나 기밀 데이터는 제출하지 마세요. - OpenAI APIs: 요청은 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data)에 따라 30일 동안 보관됩니다. - Anthropic APIs: 요청은 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage)에 따라 30일 동안 보관됩니다. diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index 2b9ecca15..139d13da8 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -94,13 +94,12 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [modell-id](/docs/config/#models) i OpenCode-konfigurasjonen din @@ -126,18 +125,16 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Modell | Inndata | Utdata | Bufret lesing | Bufret skriving | | --------------------------------- | ------- | ------- | ------------- | --------------- | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -177,8 +174,6 @@ Kredittkortgebyrer videreføres til kostpris (4.4% + $0.30 per transaction); vi Gratis-modellene: - MiniMax M2.5 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. -- MiMo V2 Pro Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. -- MiMo V2 Omni Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Nemotron 3 Super Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Big Pickle er en stealth-modell som er gratis på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. @@ -225,8 +220,6 @@ Alle modellene våre hostes i US. Leverandørene våre følger en policy for zer - Big Pickle: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - MiniMax M2.5 Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. -- MiMo V2 Pro Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. -- MiMo V2 Omni Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - Nemotron 3 Super Free (gratis NVIDIA-endepunkter): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun for prøvebruk, ikke for produksjon eller sensitive data. Prompter og svar logges av NVIDIA for å forbedre modellene og tjenestene deres. Ikke send inn personopplysninger eller konfidensielle data. - OpenAI APIs: Forespørsler lagres i 30 dager i samsvar med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Forespørsler lagres i 30 dager i samsvar med [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 57cb046b4..42a9bb3d1 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -92,6 +92,7 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -99,8 +100,6 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [ID modelu](/docs/config/#models) w Twojej konfiguracji OpenCode używa formatu @@ -126,18 +125,16 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Model | Wejście | Wyjście | Odczyt z cache | Zapis do cache | | --------------------------------- | ------- | ------- | -------------- | -------------- | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -178,8 +175,6 @@ Opłaty za karty kredytowe są przenoszone po kosztach (4.4% + $0.30 per transac Darmowe modele: - MiniMax M2.5 Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. -- MiMo V2 Pro Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. -- MiMo V2 Omni Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Nemotron 3 Super Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Big Pickle to stealth model, który jest darmowy w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. @@ -226,8 +221,6 @@ Wszystkie nasze modele są hostowane w US. Nasi dostawcy stosują politykę zero - Big Pickle: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - MiniMax M2.5 Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. -- MiMo V2 Pro Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. -- MiMo V2 Omni Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - Nemotron 3 Super Free (darmowe endpointy NVIDIA): Udostępniany zgodnie z [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Tylko do użytku próbnego, nie do produkcji ani danych wrażliwych. NVIDIA rejestruje prompty i odpowiedzi, aby ulepszać swoje modele i usługi. Nie przesyłaj danych osobowych ani poufnych. - OpenAI APIs: Żądania są przechowywane przez 30 dni zgodnie z [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Żądania są przechowywane przez 30 dni zgodnie z [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index ceead9b10..a2bb269ce 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -83,6 +83,7 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -90,8 +91,6 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | O [model id](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode/`. Por exemplo, para GPT 5.3 Codex, você usaria `opencode/gpt-5.3-codex` na sua configuração. @@ -115,18 +114,16 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Modelo | Entrada | Saída | Leitura em cache | Escrita em cache | | --------------------------------- | ------- | ------- | ---------------- | ---------------- | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -166,8 +163,6 @@ As taxas de cartão de crédito são repassadas a preço de custo (4.4% + $0.30 Os modelos gratuitos: - MiniMax M2.5 Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. -- MiMo V2 Pro Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. -- MiMo V2 Omni Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Nemotron 3 Super Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Big Pickle é um modelo stealth que está gratuito no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. @@ -211,8 +206,6 @@ Todos os nossos modelos são hospedados nos US. Nossos provedores seguem uma pol - Big Pickle: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - MiniMax M2.5 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. -- MiMo V2 Pro Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. -- MiMo V2 Omni Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - Nemotron 3 Super Free (endpoints gratuitos da NVIDIA): Fornecido sob os [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Apenas para uso de avaliação, não para produção nem dados sensíveis. A NVIDIA registra prompts e saídas para melhorar seus modelos e serviços. Não envie dados pessoais ou confidenciais. - OpenAI APIs: As solicitações são retidas por 30 dias de acordo com [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: As solicitações são retidas por 30 dias de acordo com [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 76e6877e5..8d1b11a10 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -92,6 +92,7 @@ OpenCode Zen работает как любой другой провайдер | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -99,8 +100,6 @@ OpenCode Zen работает как любой другой провайдер | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [идентификатор модели](/docs/config/#models) в вашей конфигурации OpenCode @@ -126,18 +125,16 @@ https://opencode.ai/zen/v1/models | Модель | Вход | Выход | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -177,8 +174,6 @@ https://opencode.ai/zen/v1/models Бесплатные модели: - MiniMax M2.5 Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. -- MiMo V2 Pro Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. -- MiMo V2 Omni Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Nemotron 3 Super Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Big Pickle — это скрытая модель, которая доступна бесплатно в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. @@ -225,8 +220,6 @@ https://opencode.ai/zen/v1/models - Big Pickle: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - MiniMax M2.5 Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. -- MiMo V2 Pro Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. -- MiMo V2 Omni Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - Nemotron 3 Super Free (бесплатные эндпоинты NVIDIA): предоставляется в соответствии с [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Только для пробного использования, не для продакшена и не для чувствительных данных. NVIDIA логирует запросы и ответы, чтобы улучшать свои модели и сервисы. Не отправляйте персональные или конфиденциальные данные. - OpenAI APIs: запросы хранятся 30 дней в соответствии с [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: запросы хранятся 30 дней в соответствии с [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index a7e85ac17..c3b298a32 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -87,13 +87,12 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) ใน OpenCode config ของคุณใช้รูปแบบ `opencode/` ตัวอย่างเช่น สำหรับ GPT 5.3 Codex คุณจะใช้ `opencode/gpt-5.3-codex` ใน config ของคุณ @@ -117,18 +116,16 @@ https://opencode.ai/zen/v1/models | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -168,8 +165,6 @@ https://opencode.ai/zen/v1/models โมเดลฟรี: - MiniMax M2.5 Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล -- MiMo V2 Pro Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล -- MiMo V2 Omni Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Nemotron 3 Super Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Big Pickle เป็น stealth model ที่ใช้งานฟรีบน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล @@ -213,8 +208,6 @@ https://opencode.ai/zen/v1/models - Big Pickle: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - MiniMax M2.5 Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล -- MiMo V2 Pro Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล -- MiMo V2 Omni Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - Nemotron 3 Super Free (endpoint ฟรีของ NVIDIA): ให้บริการภายใต้ [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) ใช้สำหรับการทดลองเท่านั้น ไม่เหมาะสำหรับ production หรือข้อมูลที่อ่อนไหว NVIDIA จะบันทึก prompt และ output เพื่อนำไปปรับปรุงโมเดลและบริการของตน โปรดอย่าส่งข้อมูลส่วนบุคคลหรือข้อมูลลับ. - OpenAI APIs: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index 31d7e958a..754029305 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -85,13 +85,12 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode yapılandırmanızdaki [model id](/docs/config/#models) `opencode/` biçimini kullanır. Örneğin, GPT 5.3 Codex için yapılandırmanızda `opencode/gpt-5.3-codex` kullanırsınız. @@ -115,18 +114,16 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -166,8 +163,6 @@ Kredi kartı ücretleri maliyet üzerinden yansıtılır (%4.4 + işlem başına Ücretsiz modeller: - MiniMax M2.5 Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. -- MiMo V2 Pro Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. -- MiMo V2 Omni Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Nemotron 3 Super Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Big Pickle, sınırlı bir süre için OpenCode'da ücretsiz olan gizli bir modeldir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. @@ -211,8 +206,6 @@ Tüm modellerimiz US'de barındırılıyor. Sağlayıcılarımız zero-retention - Big Pickle: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - MiniMax M2.5 Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. -- MiMo V2 Pro Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. -- MiMo V2 Omni Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - Nemotron 3 Super Free (ücretsiz NVIDIA uç noktaları): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) kapsamında sunulur. Yalnızca deneme amaçlıdır; üretim veya hassas veriler için uygun değildir. NVIDIA, modellerini ve hizmetlerini geliştirmek için promptları ve çıktıları kaydeder. Kişisel veya gizli veri göndermeyin. - OpenAI APIs: İstekler [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) uyarınca 30 gün boyunca saklanır. - Anthropic APIs: İstekler [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) uyarınca 30 gün boyunca saklanır. diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 0fd978b22..ec44a0548 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -92,6 +92,7 @@ You can also access our models through the following API endpoints. | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -126,6 +127,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Big Pickle | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index 30ab698d0..8eedcf31c 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -83,6 +83,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -90,8 +91,6 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | 在你的 OpenCode 配置中,[模型 ID](/docs/config/#models) 使用 `opencode/` 格式。例如,对于 GPT 5.3 Codex,你需要在配置中使用 `opencode/gpt-5.3-codex`。 @@ -115,18 +114,16 @@ https://opencode.ai/zen/v1/models | 模型 | 输入 | 输出 | 缓存读取 | 缓存写入 | | --------------------------------- | ------ | ------- | -------- | -------- | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -166,8 +163,6 @@ https://opencode.ai/zen/v1/models 免费模型: - MiniMax M2.5 Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 -- MiMo V2 Pro Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 -- MiMo V2 Omni Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Nemotron 3 Super Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Big Pickle 是一个隐身模型,目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 @@ -211,8 +206,6 @@ https://opencode.ai/zen/v1/models - Big Pickle:在免费期间,收集的数据可能会被用于改进模型。 - MiniMax M2.5 Free:在免费期间,收集的数据可能会被用于改进模型。 -- MiMo V2 Pro Free:在免费期间,收集的数据可能会被用于改进模型。 -- MiMo V2 Omni Free:在免费期间,收集的数据可能会被用于改进模型。 - Nemotron 3 Super Free(NVIDIA 免费端点):根据 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。仅供试用,不适用于生产环境或敏感数据。NVIDIA 会记录提示词和输出内容,以改进其模型和服务。请勿提交个人或机密数据。 - OpenAI APIs:请求会根据 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。 - Anthropic APIs:请求会根据 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 保留 30 天。 diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index a464386fd..5ccc36785 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -87,6 +87,7 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -94,8 +95,6 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode 設定中的 [模型 ID](/docs/config/#models) 會使用 `opencode/` @@ -120,18 +119,16 @@ https://opencode.ai/zen/v1/models | 模型 | 輸入 | 輸出 | 快取讀取 | 快取寫入 | | --------------------------------- | ------ | ------- | -------- | -------- | | Big Pickle | Free | Free | Free | - | -| MiMo V2 Pro Free | Free | Free | Free | - | -| MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | -| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.7 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | @@ -172,8 +169,6 @@ https://opencode.ai/zen/v1/models 免費模型: - MiniMax M2.5 Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 -- MiMo V2 Pro Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 -- MiMo V2 Omni Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Nemotron 3 Super Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Big Pickle 是一個隱身模型,在 OpenCode 上限時免費提供。團隊正在利用這段時間收集回饋並改進模型。 @@ -218,8 +213,6 @@ https://opencode.ai/zen/v1/models - Big Pickle: 在免費期間,收集到的資料可能會用於改進模型。 - MiniMax M2.5 Free: 在免費期間,收集到的資料可能會用於改進模型。 -- MiMo V2 Pro Free: 在免費期間,收集到的資料可能會用於改進模型。 -- MiMo V2 Omni Free: 在免費期間,收集到的資料可能會用於改進模型。 - Nemotron 3 Super Free(NVIDIA 免費端點):依據 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。僅供試用,不適用於正式環境或敏感資料。NVIDIA 會記錄提示詞與輸出內容,以改進其模型與服務。請勿提交個人或機密資料。 - OpenAI APIs: 請求會依據 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。 - Anthropic APIs: 請求會依據 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 保留 30 天。 From 38e2f4cddafcbc4e3ac5f8ebdcdab9d1f468737b Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:32:31 +0800 Subject: [PATCH 006/426] fix(desktop-electron): add CORS headers to main window webRequest (#23633) --- packages/desktop-electron/src/main/windows.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/desktop-electron/src/main/windows.ts b/packages/desktop-electron/src/main/windows.ts index df55e8da2..337e1ca0b 100644 --- a/packages/desktop-electron/src/main/windows.ts +++ b/packages/desktop-electron/src/main/windows.ts @@ -100,6 +100,19 @@ export function createMainWindow() { }, }) + win.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => { + const { requestHeaders } = details + upsertKeyValue(requestHeaders, "Access-Control-Allow-Origin", ["*"]) + callback({ requestHeaders }) + }) + + win.webContents.session.webRequest.onHeadersReceived((details, callback) => { + const { responseHeaders = {} } = details + upsertKeyValue(responseHeaders, "Access-Control-Allow-Origin", ["*"]) + upsertKeyValue(responseHeaders, "Access-Control-Allow-Headers", ["*"]) + callback({ responseHeaders }) + }) + state.manage(win) loadWindow(win, "index.html") wireZoom(win) @@ -177,3 +190,17 @@ function wireZoom(win: BrowserWindow) { win.webContents.setZoomFactor(1) }) } + +function upsertKeyValue(obj: Record, keyToChange: string, value: any) { + const keyToChangeLower = keyToChange.toLowerCase() + for (const key of Object.keys(obj)) { + if (key.toLowerCase() === keyToChangeLower) { + // Reassign old key + obj[key] = value + // Done + return + } + } + // Insert at end instead + obj[keyToChange] = value +} From 1e0137f624e5b370ae5e1c21b4a512889e83928d Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 21 Apr 2026 14:01:52 +0800 Subject: [PATCH 007/426] go: promote kimi k2.6 usage limits (#23634) Co-authored-by: Frank --- packages/console/app/src/i18n/ar.ts | 2 + packages/console/app/src/i18n/br.ts | 2 + packages/console/app/src/i18n/da.ts | 2 + packages/console/app/src/i18n/de.ts | 2 + packages/console/app/src/i18n/en.ts | 2 + packages/console/app/src/i18n/es.ts | 2 + packages/console/app/src/i18n/fr.ts | 2 + packages/console/app/src/i18n/it.ts | 2 + packages/console/app/src/i18n/ja.ts | 2 + packages/console/app/src/i18n/ko.ts | 2 + packages/console/app/src/i18n/no.ts | 2 + packages/console/app/src/i18n/pl.ts | 2 + packages/console/app/src/i18n/ru.ts | 2 + packages/console/app/src/i18n/th.ts | 2 + packages/console/app/src/i18n/tr.ts | 2 + packages/console/app/src/i18n/zh.ts | 2 + packages/console/app/src/i18n/zht.ts | 2 + packages/console/app/src/routes/go/index.css | 45 +++++++++++++++++++- packages/console/app/src/routes/go/index.tsx | 25 +++++++++-- 19 files changed, 99 insertions(+), 5 deletions(-) diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index 2df31a213..73c07c677 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -261,6 +261,8 @@ export const dict = { "go.cta.promo": "$5 للشهر الأول", "go.pricing.body": "استخدمه مع أي وكيل. $5 للشهر الأول، ثم $10/شهر. قم بزيادة الرصيد إذا لزم الأمر. الإلغاء في أي وقت.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6: حد الاستخدام 3 أضعاف حتى 27 أبريل", "go.graph.free": "مجاني", "go.graph.freePill": "Big Pickle ونماذج مجانية", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index 2546443e9..d79b8350a 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -265,6 +265,8 @@ export const dict = { "go.cta.promo": "$5 no primeiro mês", "go.pricing.body": "Use com qualquer agente. $5 no primeiro mês, depois $10/mês. Recarregue o crédito se necessário. Cancele a qualquer momento.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6: limite de uso 3x maior até 27 de abril", "go.graph.free": "Grátis", "go.graph.freePill": "Big Pickle e modelos gratuitos", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 6cd974c18..e80698396 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -263,6 +263,8 @@ export const dict = { "go.cta.promo": "$5 første måned", "go.pricing.body": "Brug med enhver agent. $5 første måned, derefter $10/måned. Tank op med kredit efter behov. Afmeld når som helst.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6: brugsgrænsen tredoblet til 27. april", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle og gratis modeller", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index eaf069f5a..bdd47e77c 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -265,6 +265,8 @@ export const dict = { "go.cta.promo": "$5 im ersten Monat", "go.pricing.body": "Mit jedem Agenten nutzbar. $5 im ersten Monat, danach $10/Monat. Guthaben bei Bedarf aufladen. Jederzeit kündbar.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6: Nutzungslimit bis zum 27. April verdreifacht", "go.graph.free": "Kostenlos", "go.graph.freePill": "Big Pickle und kostenlose Modelle", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index 5cfd46123..a242ff101 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -249,6 +249,8 @@ export const dict = { "go.title": "OpenCode Go | Low cost coding models for everyone", "go.meta.description": "Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6 gets 3× usage limits through April 27", "go.hero.title": "Low cost coding models for everyone", "go.hero.body": "Go brings agentic coding to programmers around the world. Offering generous limits and reliable access to the most capable open-source models, so you can build with powerful agents without worrying about cost or availability.", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 812788386..ddeff684b 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -266,6 +266,8 @@ export const dict = { "go.cta.promo": "$5 el primer mes", "go.pricing.body": "Úsalo con cualquier agente. $5 el primer mes, luego 10 $/mes. Recarga crédito si es necesario. Cancela en cualquier momento.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6: límite de uso triplicado hasta el 27 de abril", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle y modelos gratuitos", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index 53f167d7b..df892c98d 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -267,6 +267,8 @@ export const dict = { "go.cta.promo": "$5 le premier mois", "go.pricing.body": "Utilisez-le avec n'importe quel agent. $5 le premier mois, puis 10 $/mois. Rechargez du crédit si nécessaire. Annulez à tout moment.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6 : limites d’utilisation triplées jusqu’au 27 avril", "go.graph.free": "Gratuit", "go.graph.freePill": "Big Pickle et modèles gratuits", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 580dad256..67a73aaee 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -263,6 +263,8 @@ export const dict = { "go.cta.promo": "$5 il primo mese", "go.pricing.body": "Usalo con qualsiasi agente. $5 il primo mese, poi $10/mese. Ricarica il credito se necessario. Annulla in qualsiasi momento.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6: limite d'uso triplicato fino al 27 aprile", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle e modelli gratuiti", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index b21f6a00e..541fdd56c 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -262,6 +262,8 @@ export const dict = { "go.cta.promo": "初月 $5", "go.pricing.body": "どのエージェントでも使えます。最初の月$5、その後$10/月。必要に応じてクレジットを追加。いつでもキャンセルできます。", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6、4月27日まで利用上限が3倍に", "go.graph.free": "無料", "go.graph.freePill": "Big Pickleと無料モデル", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index ce1f076a4..5d459425b 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -259,6 +259,8 @@ export const dict = { "go.cta.promo": "첫 달 $5", "go.pricing.body": "어떤 에이전트와도 사용할 수 있습니다. 첫 달 $5, 이후 $10/월. 필요하면 크레딧을 충전하세요. 언제든지 취소할 수 있습니다.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6, 4월 27일까지 사용 한도 3배 확대", "go.graph.free": "무료", "go.graph.freePill": "Big Pickle 및 무료 모델", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index a4af7b8b8..af2b018b0 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -263,6 +263,8 @@ export const dict = { "go.cta.promo": "$5 første måned", "go.pricing.body": "Bruk med hvilken som helst agent. $5 første måned, deretter $10/måned. Fyll på kreditt ved behov. Avslutt når som helst.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6: bruksgrensen er tredoblet til 27. april", "go.graph.free": "Gratis", "go.graph.freePill": "Big Pickle og gratis modeller", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index d8abcf70b..f2219487b 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -264,6 +264,8 @@ export const dict = { "go.cta.promo": "$5 pierwszy miesiąc", "go.pricing.body": "Używaj z dowolnym agentem. $5 za pierwszy miesiąc, potem $10/miesiąc. Doładuj konto w razie potrzeby. Anuluj w dowolnym momencie.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6: limit użycia zwiększony 3× do 27 kwietnia", "go.graph.free": "Darmowe", "go.graph.freePill": "Big Pickle i darmowe modele", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 8d4d3d4c2..8fd76226e 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -267,6 +267,8 @@ export const dict = { "go.cta.promo": "$5 первый месяц", "go.pricing.body": "Используйте с любым агентом. $5 за первый месяц, затем $10/месяц. Пополняйте баланс при необходимости. Отменить можно в любое время.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6: лимит использования увеличен в 3 раза до 27 апреля", "go.graph.free": "Бесплатно", "go.graph.freePill": "Big Pickle и бесплатные модели", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index ebec3ab86..efe535094 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -261,6 +261,8 @@ export const dict = { "go.cta.price": "$10/เดือน", "go.cta.promo": "$5 เดือนแรก", "go.pricing.body": "ใช้กับเอเจนต์ใดก็ได้ $5 ในเดือนแรก จากนั้น $10/เดือน เติมเครดิตหากจำเป็น ยกเลิกได้ตลอดเวลา", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6 โควตาการใช้งานเพิ่มเป็น 3 เท่า ถึง 27 เม.ย.", "go.graph.free": "ฟรี", "go.graph.freePill": "Big Pickle และโมเดลฟรี", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index ccc6f1d32..114dcbdb0 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -265,6 +265,8 @@ export const dict = { "go.cta.promo": "İlk ay $5", "go.pricing.body": "Herhangi bir ajanla kullanın. İlk ay $5, sonrasında ayda 10$. Gerekirse kredi yükleyin. İstediğiniz zaman iptal edin.", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6: kullanım limiti 27 Nisan'a kadar 3 katına çıktı", "go.graph.free": "Ücretsiz", "go.graph.freePill": "Big Pickle ve ücretsiz modeller", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index f54bb6873..72a2a4570 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -252,6 +252,8 @@ export const dict = { "go.cta.price": "$10/月", "go.cta.promo": "首月 $5", "go.pricing.body": "可配合任何代理使用。首月 $5,之后 $10/月。如有需要可充值。随时取消。", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6 使用额度提升至 3 倍,限时至 4 月 27 日", "go.graph.free": "免费", "go.graph.freePill": "Big Pickle 和免费模型", "go.graph.go": "Go", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index 4076f18c0..caea5c74b 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -252,6 +252,8 @@ export const dict = { "go.cta.price": "$10/月", "go.cta.promo": "首月 $5", "go.pricing.body": "可搭配任何代理使用。首月 $5,之後 $10/月。如有需要可儲值。隨時取消。", + "go.banner.badge": "3x", + "go.banner.text": "Kimi K2.6 使用額度提升至 3 倍,限時至 4 月 27 日", "go.graph.free": "免費", "go.graph.freePill": "Big Pickle 與免費模型", "go.graph.go": "Go", diff --git a/packages/console/app/src/routes/go/index.css b/packages/console/app/src/routes/go/index.css index cd4406f25..de8dce472 100644 --- a/packages/console/app/src/routes/go/index.css +++ b/packages/console/app/src/routes/go/index.css @@ -306,7 +306,7 @@ body { [data-component="hero"] { display: flex; flex-direction: column; - padding: calc(var(--vertical-padding) * 2) var(--padding); + padding: calc(var(--vertical-padding) * 1.5) var(--padding); [data-slot="zen logo dark"] { display: none; @@ -326,6 +326,37 @@ body { } } + [data-component="desktop-app-banner"] { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 32px; + + [data-slot="badge"] { + background: var(--color-background-strong); + color: var(--color-text-inverted); + font-weight: 500; + padding: 4px 8px; + line-height: 1; + flex-shrink: 0; + } + + [data-slot="content"] { + display: flex; + align-items: center; + gap: 1ch; + } + + [data-slot="text"] { + color: var(--color-text-strong); + line-height: 1.4; + + @media (max-width: 30.625rem) { + display: none; + } + } + } + [data-slot="hero-copy"] { img { margin-bottom: 24px; @@ -544,6 +575,14 @@ body { font-weight: 600; white-space: nowrap; } + + [data-bonus] { + color: var(--color-text-weak); + font-size: 12px; + font-weight: 400; + line-height: 1; + white-space: nowrap; + } } [data-slot="plot-labels"] { @@ -623,6 +662,10 @@ body { fill: var(--color-text-strong); } + [data-bar][data-kind="promo"] { + fill: color-mix(in srgb, var(--bar-go) 50%, transparent); + } + [data-val] { fill: var(--color-text-strong); font-size: 13px; diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index b66419c5a..bae5ddd28 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -59,9 +59,8 @@ function LimitsGraph(props: { href: string }) { const free = 200 const graph = [ { id: "glm-5.1", name: "GLM-5.1", req: 880, d: "100ms" }, - { id: "kimi-k2.6", name: "Kimi K2.6", req: 1150, d: "150ms" }, + { id: "kimi-k2.6", name: "Kimi K2.6 (3x usage)", req: 3450, baseReq: 1150, d: "150ms" }, { id: "mimo-v2-pro", name: "MiMo-V2-Pro", req: 1290, d: "150ms" }, - { id: "kimi-k2.5", name: "Kimi K2.5", req: 1850, d: "240ms" }, { id: "qwen3.6-plus", name: "Qwen3.6 Plus", req: 3300, d: "280ms" }, { id: "minimax-m2.7", name: "MiniMax M2.7", req: 3400, d: "300ms" }, { id: "qwen3.5-plus", name: "Qwen3.5 Plus", req: 10200, d: "360ms" }, @@ -79,7 +78,7 @@ function LimitsGraph(props: { href: string }) { const rmax = Math.max(1, ...graph.map((m) => ratio(m.req))) const log = (n: number) => Math.log10(Math.max(n, 1)) const base = 24 - const p = 1.8 + const p = 2.2 const x = (r: number) => left + base + Math.pow(log(r) / log(rmax), p) * (plot - base) const start = (x(1) / w) * 100 @@ -152,12 +151,24 @@ function LimitsGraph(props: { href: string }) { + {m.baseReq && ( + + )} )} @@ -247,6 +258,12 @@ export default function Home() {
+
+ {i18n.t("home.banner.badge")} +
+ {i18n.t("go.banner.text")} +
+
From 22d33c57af94f3bac5022f64ec11c82a06c015c8 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:11:23 +0800 Subject: [PATCH 008/426] fix(app): properly wrap produce calls in setProjects (#23638) --- packages/app/src/context/global-sync.tsx | 11 +++-------- .../src/context/global-sync/event-reducer.ts | 18 +++++++++++------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 4ced2b939..313ff2965 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -10,7 +10,7 @@ import type { import { showToast } from "@opencode-ai/ui/toast" import { getFilename } from "@opencode-ai/shared/util/path" import { batch, createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js" -import { createStore, produce, reconcile } from "solid-js/store" +import { createStore, produce, reconcile, unwrap } from "solid-js/store" import { useLanguage } from "@/context/language" import { Persist, persisted } from "@/utils/persist" import type { InitError } from "../pages/error" @@ -95,13 +95,8 @@ function createGlobalSync() { ) } - const setProjects = (next: Project[] | ((draft: Project[]) => void)) => { + const setProjects = (next: Project[] | ((draft: Project[]) => Project[])) => { projectWritten = true - if (typeof next === "function") { - setGlobalStore("project", produce(next)) - cacheProjects() - return - } setGlobalStore("project", next) cacheProjects() } @@ -116,7 +111,7 @@ function createGlobalSync() { const set = ((...input: unknown[]) => { if (input[0] === "project" && (Array.isArray(input[1]) || typeof input[1] === "function")) { - setProjects(input[1] as Project[] | ((draft: Project[]) => void)) + setProjects(input[1] as Project[] | ((draft: Project[]) => Project[])) return input[1] } return (setGlobalStore as (...args: unknown[]) => unknown)(...input) diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts index 11a0cf83f..82408fdfe 100644 --- a/packages/app/src/context/global-sync/event-reducer.ts +++ b/packages/app/src/context/global-sync/event-reducer.ts @@ -21,7 +21,7 @@ const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"]) export function applyGlobalEvent(input: { event: { type: string; properties?: unknown } project: Project[] - setGlobalProject: (next: Project[] | ((draft: Project[]) => void)) => void + setGlobalProject: (next: Project[] | ((draft: Project[]) => Project[])) => void refresh: () => void }) { if (input.event.type === "global.disposed" || input.event.type === "server.connected") { @@ -33,14 +33,18 @@ export function applyGlobalEvent(input: { const properties = input.event.properties as Project const result = Binary.search(input.project, properties.id, (s) => s.id) if (result.found) { - input.setGlobalProject((draft) => { - draft[result.index] = { ...draft[result.index], ...properties } - }) + input.setGlobalProject( + produce((draft) => { + draft[result.index] = { ...draft[result.index], ...properties } + }), + ) return } - input.setGlobalProject((draft) => { - draft.splice(result.index, 0, properties) - }) + input.setGlobalProject( + produce((draft) => { + draft.splice(result.index, 0, properties) + }), + ) } function cleanupSessionCaches( From 8a7bb7c6a9fe4fb9b1f85561603cf131278ccf54 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 21 Apr 2026 02:36:38 -0400 Subject: [PATCH 009/426] zen: tpm routing --- .../console/app/src/routes/zen/util/handler.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 635eadebe..d9dc45001 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -448,35 +448,28 @@ export async function handler( return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider) } - // Filter out TPM limited providers - const allProviders = modelInfo.providers.filter((provider) => { - if (!provider.tpmLimit) return true - const usage = modelTpmLimits?.[`${provider.id}/${provider.model}`] ?? 0 - return usage < provider.tpmLimit * 1_000_000 - }) - // Always use the same provider for the same session if (stickyProvider) { - const provider = allProviders.find((provider) => provider.id === stickyProvider) + const provider = modelInfo.providers.find((provider) => provider.id === stickyProvider) if (provider) return provider } if (trialProviders) { const trialProvider = trialProviders[Math.floor(Math.random() * trialProviders.length)] - const provider = allProviders.find((provider) => provider.id === trialProvider) + const provider = modelInfo.providers.find((provider) => provider.id === trialProvider) if (provider) return provider } if (retry.retryCount !== MAX_FAILOVER_RETRIES) { let topPriority = Infinity - const providers = allProviders + const providers = modelInfo.providers .filter((provider) => !provider.disabled) .filter((provider) => provider.weight !== 0) .filter((provider) => !retry.excludeProviders.includes(provider.id)) .filter((provider) => { if (!provider.tpmLimit) return true const usage = modelTpmLimits?.[`${provider.id}/${provider.model}`] ?? 0 - return usage < provider.tpmLimit * 1_000_000 * 0.8 + return usage < provider.tpmLimit * 1_000_000 }) .map((provider) => { topPriority = Math.min(topPriority, provider.priority) From 224548d87d9aa9b8fdbcba2a8c1f96d5f2679ffa Mon Sep 17 00:00:00 2001 From: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Date: Tue, 21 Apr 2026 08:38:56 +0200 Subject: [PATCH 010/426] fix(desktop): adjust layout properties in DialogSelectServer component (#23589) --- packages/app/src/components/dialog-select-server.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx index dd92edec3..0cb5a2d60 100644 --- a/packages/app/src/components/dialog-select-server.tsx +++ b/packages/app/src/components/dialog-select-server.tsx @@ -504,7 +504,7 @@ export function DialogSelectServer() { return ( -
+
{(i) => { const key = ServerConnection.key(i) @@ -619,7 +619,7 @@ export function DialogSelectServer() { -
+
Date: Tue, 21 Apr 2026 17:54:53 +1000 Subject: [PATCH 011/426] fix(core): use file:// URLs for local dynamic import() on Windows+Node (#23639) --- packages/opencode/src/provider/provider.ts | 6 +++++- packages/opencode/src/tool/registry.ts | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 6e116fe41..d643f2537 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -19,6 +19,7 @@ import { zod } from "@/util/effect-zod" import { iife } from "@/util/iife" import { Global } from "../global" import path from "path" +import { pathToFileURL } from "url" import { Effect, Layer, Context, Schema, Types } from "effect" import { EffectBridge } from "@/effect" import { InstanceState } from "@/effect" @@ -1506,7 +1507,10 @@ const layer: Layer.Layer< installedPath = model.api.npm } - const mod = await import(installedPath) + // `installedPath` is a local entry path or an existing `file://` URL. Normalize + // only path inputs so Node on Windows accepts the dynamic import. + const importSpec = installedPath.startsWith("file://") ? installedPath : pathToFileURL(installedPath).href + const mod = await import(importSpec) const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!] const loaded = fn({ diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index e27593e59..0211e33bc 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -157,9 +157,9 @@ export const layer: Layer.Layer< if (matches.length) yield* config.waitForDependencies() for (const match of matches) { const namespace = path.basename(match, path.extname(match)) - const mod = yield* Effect.promise( - () => import(process.platform === "win32" ? match : pathToFileURL(match).href), - ) + // `match` is an absolute filesystem path from `Glob.scanSync(..., { absolute: true })`. + // Import it as `file://` so Node on Windows accepts the dynamic import. + const mod = yield* Effect.promise(() => import(pathToFileURL(match).href)) for (const [id, def] of Object.entries(mod)) { custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def)) } From febadc5589401a3112ba7788e7fb5837fbf95d3e Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:49:04 +0800 Subject: [PATCH 012/426] fix(ui): correct diff render condition logic (#23670) --- packages/ui/src/components/session-review.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 6e2b0853a..94bca6727 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -388,7 +388,7 @@ export const SessionReview = (props: SessionReviewProps) => { const file = diff.file // binary files have empty diffs that we can't render - const diffCanRender = () => diff.additions !== 0 && diff.deletions !== 0 + const diffCanRender = () => diff.additions !== 0 || diff.deletions !== 0 const expanded = createMemo(() => open().includes(file)) const mounted = createMemo(() => expanded() && (!!store.visible[file] || pinned(file))) From 811a7e9a8bf04f93eff6b9efdec7c87991aead55 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:10:50 +0800 Subject: [PATCH 013/426] feat(app): allow disabling progress bar in settings (#23674) --- packages/app/src/components/settings-general.tsx | 12 ++++++++++++ packages/app/src/context/settings.tsx | 9 +++++++++ packages/app/src/i18n/ar.ts | 3 +++ packages/app/src/i18n/br.ts | 3 +++ packages/app/src/i18n/bs.ts | 3 +++ packages/app/src/i18n/da.ts | 3 +++ packages/app/src/i18n/de.ts | 3 +++ packages/app/src/i18n/en.ts | 3 +++ packages/app/src/i18n/es.ts | 3 +++ packages/app/src/i18n/fr.ts | 3 +++ packages/app/src/i18n/ja.ts | 3 +++ packages/app/src/i18n/ko.ts | 3 +++ packages/app/src/i18n/no.ts | 3 +++ packages/app/src/i18n/pl.ts | 3 +++ packages/app/src/i18n/ru.ts | 3 +++ packages/app/src/i18n/th.ts | 3 +++ packages/app/src/i18n/tr.ts | 4 ++++ packages/app/src/i18n/zh.ts | 2 ++ packages/app/src/i18n/zht.ts | 2 ++ packages/app/src/pages/session/message-timeline.tsx | 2 +- 20 files changed, 72 insertions(+), 1 deletion(-) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 490bc2e48..13651aac0 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -280,6 +280,18 @@ export const SettingsGeneral: Component = () => { />
+ + +
+ settings.general.setShowSessionProgressBar(checked)} + /> +
+
) diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx index 6d4f3d2cd..be2fb49d7 100644 --- a/packages/app/src/context/settings.tsx +++ b/packages/app/src/context/settings.tsx @@ -31,6 +31,7 @@ export interface Settings { showReasoningSummaries: boolean shellToolPartsExpanded: boolean editToolPartsExpanded: boolean + showSessionProgressBar: boolean } updates: { startup: boolean @@ -115,6 +116,7 @@ const defaultSettings: Settings = { showReasoningSummaries: false, shellToolPartsExpanded: false, editToolPartsExpanded: false, + showSessionProgressBar: true, }, updates: { startup: true, @@ -227,6 +229,13 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont setEditToolPartsExpanded(value: boolean) { setStore("general", "editToolPartsExpanded", value) }, + showSessionProgressBar: withFallback( + () => store.general?.showSessionProgressBar, + defaultSettings.general.showSessionProgressBar, + ), + setShowSessionProgressBar(value: boolean) { + setStore("general", "showSessionProgressBar", value) + }, }, updates: { startup: withFallback(() => store.updates?.startup, defaultSettings.updates.startup), diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 9e9a88c2d..e3f1209f2 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -582,6 +582,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit", "settings.general.row.editToolPartsExpanded.description": "إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني", + "settings.general.row.showSessionProgressBar.title": "إظهار شريط تقدم الجلسة", + "settings.general.row.showSessionProgressBar.description": + "عرض شريط التقدم المتحرك أعلى الجلسة أثناء عمل الوكيل", "settings.general.row.wayland.title": "استخدام Wayland الأصلي", "settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 5fd1aee76..022d01298 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -590,6 +590,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição", "settings.general.row.editToolPartsExpanded.description": "Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo", + "settings.general.row.showSessionProgressBar.title": "Mostrar barra de progresso da sessão", + "settings.general.row.showSessionProgressBar.description": + "Exibir a barra de progresso animada no topo da sessão quando o agente estiver trabalhando", "settings.general.row.wayland.title": "Usar Wayland nativo", "settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index f872db1f0..15d8376ab 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -655,6 +655,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje", "settings.general.row.editToolPartsExpanded.description": "Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci", + "settings.general.row.showSessionProgressBar.title": "Prikaži traku napretka sesije", + "settings.general.row.showSessionProgressBar.description": + "Prikaži animiranu traku napretka na vrhu sesije kada agent radi", "settings.general.row.wayland.title": "Koristi nativni Wayland", "settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 82f4fe3f6..03cfe2b78 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -649,6 +649,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele", "settings.general.row.editToolPartsExpanded.description": "Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen", + "settings.general.row.showSessionProgressBar.title": "Vis sessionens fremdriftslinje", + "settings.general.row.showSessionProgressBar.description": + "Vis den animerede fremdriftslinje øverst i sessionen, når agenten arbejder", "settings.general.row.wayland.title": "Brug native Wayland", "settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index d5b95459a..ccb88e9f4 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -601,6 +601,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen", "settings.general.row.editToolPartsExpanded.description": "Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen", + "settings.general.row.showSessionProgressBar.title": "Sitzungsfortschrittsleiste anzeigen", + "settings.general.row.showSessionProgressBar.description": + "Die animierte Fortschrittsleiste oben in der Sitzung anzeigen, wenn der Agent arbeitet", "settings.general.row.wayland.title": "Natives Wayland verwenden", "settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 8a2fbf87f..ed80b38ce 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -762,6 +762,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Expand edit tool parts", "settings.general.row.editToolPartsExpanded.description": "Show edit, write, and patch tool parts expanded by default in the timeline", + "settings.general.row.showSessionProgressBar.title": "Show session progress bar", + "settings.general.row.showSessionProgressBar.description": + "Display the animated progress bar at the top of the session when the agent is working", "settings.general.row.wayland.title": "Use native Wayland", "settings.general.row.wayland.description": "Disable X11 fallback on Wayland. Requires restart.", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 12bc45cf3..0b4789c2a 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -659,6 +659,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición", "settings.general.row.editToolPartsExpanded.description": "Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo", + "settings.general.row.showSessionProgressBar.title": "Mostrar barra de progreso de la sesión", + "settings.general.row.showSessionProgressBar.description": + "Mostrar la barra de progreso animada en la parte superior de la sesión cuando el agente esté trabajando", "settings.general.row.wayland.title": "Usar Wayland nativo", "settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 6c98b9ca1..4d73f626b 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -598,6 +598,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit", "settings.general.row.editToolPartsExpanded.description": "Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie", + "settings.general.row.showSessionProgressBar.title": "Afficher la barre de progression de la session", + "settings.general.row.showSessionProgressBar.description": + "Afficher la barre de progression animée en haut de la session lorsque l'agent travaille", "settings.general.row.wayland.title": "Utiliser Wayland natif", "settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 767833412..493b1f17f 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -587,6 +587,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開", "settings.general.row.editToolPartsExpanded.description": "タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します", + "settings.general.row.showSessionProgressBar.title": "セッション進行状況バーを表示", + "settings.general.row.showSessionProgressBar.description": + "エージェントの作業中に、セッション上部にアニメーション付きの進行状況バーを表示します", "settings.general.row.wayland.title": "ネイティブWaylandを使用", "settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 76bf33df6..0218cc1a9 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -583,6 +583,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "edit 도구 파트 펼치기", "settings.general.row.editToolPartsExpanded.description": "타임라인에서 기본적으로 edit, write, patch 도구 파트를 펼친 상태로 표시합니다", + "settings.general.row.showSessionProgressBar.title": "세션 진행 표시줄 표시", + "settings.general.row.showSessionProgressBar.description": + "에이전트가 작업 중일 때 세션 상단에 애니메이션 진행 표시줄을 표시합니다", "settings.general.row.wayland.title": "네이티브 Wayland 사용", "settings.general.row.wayland.description": "Wayland에서 X11 폴백을 비활성화합니다. 다시 시작해야 합니다.", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 75e557b16..43aa84420 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -656,6 +656,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Utvid edit-verktøydeler", "settings.general.row.editToolPartsExpanded.description": "Vis edit-, write- og patch-verktøydeler utvidet som standard i tidslinjen", + "settings.general.row.showSessionProgressBar.title": "Vis fremdriftslinje for sesjonen", + "settings.general.row.showSessionProgressBar.description": + "Vis den animerte fremdriftslinjen øverst i sesjonen når agenten jobber", "settings.general.row.wayland.title": "Bruk innebygd Wayland", "settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Krever omstart.", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 0ab4a6906..6c6d4dddc 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -588,6 +588,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit", "settings.general.row.editToolPartsExpanded.description": "Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu", + "settings.general.row.showSessionProgressBar.title": "Pokazuj pasek postępu sesji", + "settings.general.row.showSessionProgressBar.description": + "Wyświetlaj animowany pasek postępu u góry sesji, gdy agent pracuje", "settings.general.row.wayland.title": "Użyj natywnego Wayland", "settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 135c8e66c..e0b094877 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -656,6 +656,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit", "settings.general.row.editToolPartsExpanded.description": "Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию", + "settings.general.row.showSessionProgressBar.title": "Показывать индикатор прогресса сессии", + "settings.general.row.showSessionProgressBar.description": + "Показывать анимированный индикатор прогресса вверху сессии, когда агент работает", "settings.general.row.wayland.title": "Использовать нативный Wayland", "settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.", "settings.general.row.wayland.tooltip": diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 81674df32..8a15f29c0 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -647,6 +647,9 @@ export const dict = { "settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit", "settings.general.row.editToolPartsExpanded.description": "แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์", + "settings.general.row.showSessionProgressBar.title": "แสดงแถบความคืบหน้าของเซสชัน", + "settings.general.row.showSessionProgressBar.description": + "แสดงแถบความคืบหน้าแบบเคลื่อนไหวที่ด้านบนของเซสชันเมื่อเอเจนต์กำลังทำงาน", "settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ", "settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท", "settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า", diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index f3cb3ab46..f20c05000 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -663,6 +663,10 @@ export const dict = { "settings.general.row.editToolPartsExpanded.description": "Zaman çizelgesinde düzenleme, yazma ve yama araç bileşenlerini varsayılan olarak genişletilmiş göster", + "settings.general.row.showSessionProgressBar.title": "Oturum ilerleme çubuğunu göster", + "settings.general.row.showSessionProgressBar.description": + "Ajan çalışırken oturumun üst kısmında animasyonlu ilerleme çubuğunu göster", + "settings.general.row.wayland.title": "Yerel Wayland kullan", "settings.general.row.wayland.description": "Wayland'da X11 geri dönüşünü devre dışı bırak. Yeniden başlatma gerektirir.", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index d95bfd19b..05310df96 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -646,6 +646,8 @@ export const dict = { "settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分", "settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分", "settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分", + "settings.general.row.showSessionProgressBar.title": "显示会话进度条", + "settings.general.row.showSessionProgressBar.description": "当智能体正在工作时,在会话顶部显示动画进度条", "settings.general.row.wayland.title": "使用原生 Wayland", "settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。", "settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 4a88ca4fc..43681c779 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -642,6 +642,8 @@ export const dict = { "settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊", "settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊", "settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊", + "settings.general.row.showSessionProgressBar.title": "顯示工作階段進度列", + "settings.general.row.showSessionProgressBar.description": "當代理程式正在運作時,在工作階段頂部顯示動畫進度列", "settings.general.row.wayland.title": "使用原生 Wayland", "settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。", "settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。", diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index 978f188b6..9e0fed11c 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -721,7 +721,7 @@ export function MessageTimeline(props: { "md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered, }} > - +
Date: Tue, 21 Apr 2026 11:11:54 +0000 Subject: [PATCH 014/426] chore: generate --- packages/app/src/i18n/ar.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index e3f1209f2..efb2919a5 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -583,8 +583,7 @@ export const dict = { "settings.general.row.editToolPartsExpanded.description": "إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني", "settings.general.row.showSessionProgressBar.title": "إظهار شريط تقدم الجلسة", - "settings.general.row.showSessionProgressBar.description": - "عرض شريط التقدم المتحرك أعلى الجلسة أثناء عمل الوكيل", + "settings.general.row.showSessionProgressBar.description": "عرض شريط التقدم المتحرك أعلى الجلسة أثناء عمل الوكيل", "settings.general.row.wayland.title": "استخدام Wayland الأصلي", "settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.", "settings.general.row.wayland.tooltip": From 8cc2c81d57f7c3ca8942d0e2461bc676bd25e8cc Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Tue, 21 Apr 2026 19:12:32 +0800 Subject: [PATCH 015/426] fix(app): prevent prompt input animations from rerunning on every render (#23676) --- packages/app/src/components/prompt-input.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 1131baa49..06c91c292 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -54,7 +54,7 @@ import { PromptImageAttachments } from "./prompt-input/image-attachments" import { PromptDragOverlay } from "./prompt-input/drag-overlay" import { promptPlaceholder } from "./prompt-input/placeholder" import { ImagePreview } from "@opencode-ai/ui/image-preview" -import { useQueries, useQuery } from "@tanstack/solid-query" +import { useQueries } from "@tanstack/solid-query" import { loadAgentsQuery, loadProvidersQuery } from "@/context/global-sync/bootstrap" interface PromptInputProps { @@ -1257,7 +1257,9 @@ export const PromptInput: Component = (props) => { })) const agentsLoading = () => agentsQuery.isLoading + const agentsShouldFadeIn = createMemo((prev) => prev ?? agentsLoading()) const providersLoading = () => agentsLoading() || providersQuery.isLoading || globalProvidersQuery.isLoading + const providersShouldFadeIn = createMemo((prev) => prev ?? providersLoading()) const [promptReady] = createResource( () => prompt.ready().promise, @@ -1460,7 +1462,10 @@ export const PromptInput: Component = (props) => {
-
+
= (props) => { -
+
0} fallback={ @@ -1557,7 +1565,10 @@ export const PromptInput: Component = (props) => {
-
+
Date: Tue, 21 Apr 2026 10:42:54 -0400 Subject: [PATCH 016/426] fix(core): fix permissions routing when using remote workspace (#23593) --- .../opencode/src/cli/cmd/tui/routes/session/permission.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index 54cc86a40..e48f348b9 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -9,6 +9,7 @@ import { useSDK } from "../../context/sdk" import { SplitBorder } from "../../component/border" import { useSync } from "../../context/sync" import { useTextareaKeybindings } from "../../component/textarea-keybindings" +import { useProject } from "../../context/project" import path from "path" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import { Keybind } from "@/util" @@ -131,6 +132,7 @@ function TextBody(props: { title: string; description?: string; icon?: string }) export function PermissionPrompt(props: { request: PermissionRequest }) { const sdk = useSDK() + const project = useProject() const sync = useSync() const [store, setStore] = createStore({ stage: "permission" as PermissionStage, @@ -187,6 +189,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { void sdk.client.permission.reply({ reply: "always", requestID: props.request.id, + workspace: project.workspace.current(), }) }} /> @@ -198,6 +201,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { reply: "reject", requestID: props.request.id, message: message || undefined, + workspace: project.workspace.current(), }) }} onCancel={() => { @@ -450,12 +454,14 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { void sdk.client.permission.reply({ reply: "reject", requestID: props.request.id, + workspace: project.workspace.current(), }) return } void sdk.client.permission.reply({ reply: "once", requestID: props.request.id, + workspace: project.workspace.current(), }) }} /> From 2486621ca1b9d35ed15ee6c2ff2a04ba46c8e02a Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 21 Apr 2026 11:31:20 -0400 Subject: [PATCH 017/426] chore: kill unused tool (#23701) --- .../opencode/specs/effect/instance-context.md | 1 - packages/opencode/specs/effect/schema.md | 1 - packages/opencode/specs/effect/tools.md | 2 - packages/opencode/src/config/agent.ts | 2 +- packages/opencode/src/config/config.ts | 2 +- packages/opencode/src/permission/index.ts | 2 +- packages/opencode/src/tool/multiedit.ts | 61 ------------------- packages/opencode/src/tool/multiedit.txt | 41 ------------- packages/opencode/test/agent/agent.test.ts | 2 +- packages/opencode/test/config/config.test.ts | 29 --------- .../opencode/test/permission/next.test.ts | 5 +- .../web/src/content/docs/ar/permissions.mdx | 2 +- packages/web/src/content/docs/ar/tools.mdx | 4 +- .../web/src/content/docs/bs/permissions.mdx | 2 +- packages/web/src/content/docs/bs/tools.mdx | 4 +- .../web/src/content/docs/da/permissions.mdx | 2 +- packages/web/src/content/docs/da/tools.mdx | 4 +- .../web/src/content/docs/de/permissions.mdx | 2 +- packages/web/src/content/docs/de/tools.mdx | 4 +- .../web/src/content/docs/es/permissions.mdx | 2 +- packages/web/src/content/docs/es/tools.mdx | 4 +- .../web/src/content/docs/fr/permissions.mdx | 2 +- packages/web/src/content/docs/fr/tools.mdx | 4 +- .../web/src/content/docs/it/permissions.mdx | 2 +- packages/web/src/content/docs/it/tools.mdx | 4 +- .../web/src/content/docs/ja/permissions.mdx | 2 +- packages/web/src/content/docs/ja/tools.mdx | 4 +- .../web/src/content/docs/ko/permissions.mdx | 2 +- packages/web/src/content/docs/ko/tools.mdx | 4 +- .../web/src/content/docs/nb/permissions.mdx | 2 +- packages/web/src/content/docs/nb/tools.mdx | 4 +- packages/web/src/content/docs/permissions.mdx | 2 +- .../web/src/content/docs/pl/permissions.mdx | 2 +- packages/web/src/content/docs/pl/tools.mdx | 4 +- .../src/content/docs/pt-br/permissions.mdx | 2 +- packages/web/src/content/docs/pt-br/tools.mdx | 4 +- .../web/src/content/docs/ru/permissions.mdx | 2 +- packages/web/src/content/docs/ru/tools.mdx | 4 +- .../web/src/content/docs/th/permissions.mdx | 2 +- packages/web/src/content/docs/th/tools.mdx | 4 +- packages/web/src/content/docs/tools.mdx | 4 +- .../web/src/content/docs/tr/permissions.mdx | 2 +- packages/web/src/content/docs/tr/tools.mdx | 4 +- .../src/content/docs/zh-cn/permissions.mdx | 2 +- packages/web/src/content/docs/zh-cn/tools.mdx | 4 +- .../src/content/docs/zh-tw/permissions.mdx | 2 +- packages/web/src/content/docs/zh-tw/tools.mdx | 4 +- 47 files changed, 60 insertions(+), 196 deletions(-) delete mode 100644 packages/opencode/src/tool/multiedit.ts delete mode 100644 packages/opencode/src/tool/multiedit.txt diff --git a/packages/opencode/specs/effect/instance-context.md b/packages/opencode/specs/effect/instance-context.md index 7d0d7eb13..6d6371503 100644 --- a/packages/opencode/specs/effect/instance-context.md +++ b/packages/opencode/specs/effect/instance-context.md @@ -224,7 +224,6 @@ These tools mostly use direct getters for path resolution and repo-relative disp - `src/tool/bash.ts` - `src/tool/edit.ts` - `src/tool/lsp.ts` -- `src/tool/multiedit.ts` - `src/tool/plan.ts` - `src/tool/read.ts` - `src/tool/write.ts` diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 72ee10350..2fcbfc12b 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -216,7 +216,6 @@ emitted JSON Schema must stay byte-identical. - [ ] `src/tool/grep.ts` - [ ] `src/tool/invalid.ts` - [ ] `src/tool/lsp.ts` -- [ ] `src/tool/multiedit.ts` - [ ] `src/tool/plan.ts` - [ ] `src/tool/question.ts` - [ ] `src/tool/read.ts` diff --git a/packages/opencode/specs/effect/tools.md b/packages/opencode/specs/effect/tools.md index 7b4783170..3cc277357 100644 --- a/packages/opencode/specs/effect/tools.md +++ b/packages/opencode/specs/effect/tools.md @@ -46,7 +46,6 @@ These exported tool definitions currently use `Tool.define(...)` in `src/tool`: - [x] `grep.ts` - [x] `invalid.ts` - [x] `lsp.ts` -- [x] `multiedit.ts` - [x] `plan.ts` - [x] `question.ts` - [x] `read.ts` @@ -82,7 +81,6 @@ Notable items that are already effectively on the target path and do not need se - `write.ts` - `codesearch.ts` - `websearch.ts` -- `multiedit.ts` - `edit.ts` ## Filesystem notes diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index 1469522d9..85a214e12 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -93,7 +93,7 @@ const normalize = (agent: z.infer) => { const permission: ConfigPermission.Info = {} for (const [tool, enabled] of Object.entries(agent.tools ?? {})) { const action = enabled ? "allow" : "deny" - if (tool === "write" || tool === "edit" || tool === "patch" || tool === "multiedit") { + if (tool === "write" || tool === "edit" || tool === "patch") { permission.edit = action continue } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 55684fc70..cbe7cf7a2 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -660,7 +660,7 @@ export const layer = Layer.effect( const perms: Record = {} for (const [tool, enabled] of Object.entries(result.tools)) { const action: ConfigPermission.Action = enabled ? "allow" : "deny" - if (tool === "write" || tool === "edit" || tool === "patch" || tool === "multiedit") { + if (tool === "write" || tool === "edit" || tool === "patch") { perms.edit = action continue } diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index b9a221155..caf66cc94 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -307,7 +307,7 @@ export function merge(...rulesets: Ruleset[]): Ruleset { return rulesets.flat() } -const EDIT_TOOLS = ["edit", "write", "apply_patch", "multiedit"] +const EDIT_TOOLS = ["edit", "write", "apply_patch"] export function disabled(tools: string[], ruleset: Ruleset): Set { const result = new Set() diff --git a/packages/opencode/src/tool/multiedit.ts b/packages/opencode/src/tool/multiedit.ts deleted file mode 100644 index 004d3c870..000000000 --- a/packages/opencode/src/tool/multiedit.ts +++ /dev/null @@ -1,61 +0,0 @@ -import z from "zod" -import { Effect } from "effect" -import * as Tool from "./tool" -import { EditTool } from "./edit" -import DESCRIPTION from "./multiedit.txt" -import path from "path" -import { Instance } from "../project/instance" - -export const MultiEditTool = Tool.define( - "multiedit", - Effect.gen(function* () { - const editInfo = yield* EditTool - const edit = yield* editInfo.init() - - return { - description: DESCRIPTION, - parameters: z.object({ - filePath: z.string().describe("The absolute path to the file to modify"), - edits: z - .array( - z.object({ - filePath: z.string().describe("The absolute path to the file to modify"), - oldString: z.string().describe("The text to replace"), - newString: z.string().describe("The text to replace it with (must be different from oldString)"), - replaceAll: z.boolean().optional().describe("Replace all occurrences of oldString (default false)"), - }), - ) - .describe("Array of edit operations to perform sequentially on the file"), - }), - execute: ( - params: { - filePath: string - edits: Array<{ filePath: string; oldString: string; newString: string; replaceAll?: boolean }> - }, - ctx: Tool.Context, - ) => - Effect.gen(function* () { - const results = [] - for (const [, entry] of params.edits.entries()) { - const result = yield* edit.execute( - { - filePath: params.filePath, - oldString: entry.oldString, - newString: entry.newString, - replaceAll: entry.replaceAll, - }, - ctx, - ) - results.push(result) - } - return { - title: path.relative(Instance.worktree, params.filePath), - metadata: { - results: results.map((r) => r.metadata), - }, - output: results.at(-1)!.output, - } - }), - } - }), -) diff --git a/packages/opencode/src/tool/multiedit.txt b/packages/opencode/src/tool/multiedit.txt deleted file mode 100644 index bb4815124..000000000 --- a/packages/opencode/src/tool/multiedit.txt +++ /dev/null @@ -1,41 +0,0 @@ -This is a tool for making multiple edits to a single file in one operation. It is built on top of the Edit tool and allows you to perform multiple find-and-replace operations efficiently. Prefer this tool over the Edit tool when you need to make multiple edits to the same file. - -Before using this tool: - -1. Use the Read tool to understand the file's contents and context -2. Verify the directory path is correct - -To make multiple file edits, provide the following: -1. file_path: The absolute path to the file to modify (must be absolute, not relative) -2. edits: An array of edit operations to perform, where each edit contains: - - oldString: The text to replace (must match the file contents exactly, including all whitespace and indentation) - - newString: The edited text to replace the oldString - - replaceAll: Replace all occurrences of oldString. This parameter is optional and defaults to false. - -IMPORTANT: -- All edits are applied in sequence, in the order they are provided -- Each edit operates on the result of the previous edit -- All edits must be valid for the operation to succeed - if any edit fails, none will be applied -- This tool is ideal when you need to make several changes to different parts of the same file - -CRITICAL REQUIREMENTS: -1. All edits follow the same requirements as the single Edit tool -2. The edits are atomic - either all succeed or none are applied -3. Plan your edits carefully to avoid conflicts between sequential operations - -WARNING: -- The tool will fail if edits.oldString doesn't match the file contents exactly (including whitespace) -- The tool will fail if edits.oldString and edits.newString are the same -- Since edits are applied in sequence, ensure that earlier edits don't affect the text that later edits are trying to find - -When making edits: -- Ensure all edits result in idiomatic, correct code -- Do not leave the code in a broken state -- Always use absolute file paths (starting with /) -- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked. -- Use replaceAll for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance. - -If you want to create a new file, use: -- A new file path, including dir name if needed -- First edit: empty oldString and the new file's contents as newString -- Subsequent edits: normal edit operations on the created content diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 7e9a6fe90..50a3668f9 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -474,7 +474,7 @@ test("legacy tools config converts to permissions", async () => { }) }) -test("legacy tools config maps write/edit/patch/multiedit to edit permission", async () => { +test("legacy tools config maps write/edit/patch to edit permission", async () => { await using tmp = await tmpdir({ config: { agent: { diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 9f2bf9db9..3fafdadaa 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1427,35 +1427,6 @@ test("migrates legacy patch tool to edit permission", async () => { }) }) -test("migrates legacy multiedit tool to edit permission", async () => { - await using tmp = await tmpdir({ - init: async (dir) => { - await Filesystem.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - agent: { - test: { - tools: { - multiedit: false, - }, - }, - }, - }), - ) - }, - }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const config = await load() - expect(config.agent?.["test"]?.permission).toEqual({ - edit: "deny", - }) - }, - }) -}) - test("migrates mixed legacy tools config", async () => { await using tmp = await tmpdir({ init: async (dir) => { diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index d654d4b87..21a9d8400 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -422,9 +422,9 @@ test("disabled - disables tool when denied", () => { expect(result.has("read")).toBe(false) }) -test("disabled - disables edit/write/apply_patch/multiedit when edit denied", () => { +test("disabled - disables edit/write/apply_patch when edit denied", () => { const result = Permission.disabled( - ["edit", "write", "apply_patch", "multiedit", "bash"], + ["edit", "write", "apply_patch", "bash"], [ { permission: "*", pattern: "*", action: "allow" }, { permission: "edit", pattern: "*", action: "deny" }, @@ -433,7 +433,6 @@ test("disabled - disables edit/write/apply_patch/multiedit when edit denied", () expect(result.has("edit")).toBe(true) expect(result.has("write")).toBe(true) expect(result.has("apply_patch")).toBe(true) - expect(result.has("multiedit")).toBe(true) expect(result.has("bash")).toBe(false) }) diff --git a/packages/web/src/content/docs/ar/permissions.mdx b/packages/web/src/content/docs/ar/permissions.mdx index bb21d00b2..2f4d1c47c 100644 --- a/packages/web/src/content/docs/ar/permissions.mdx +++ b/packages/web/src/content/docs/ar/permissions.mdx @@ -130,7 +130,7 @@ description: تحكّم في الإجراءات التي تتطلب موافقة تُعرَّف أذونات OpenCode بأسماء الأدوات، بالإضافة إلى بعض حواجز الأمان: - `read` — قراءة ملف (يطابق مسار الملف) -- `edit` — جميع تعديلات الملفات (يشمل `edit` و`write` و`patch` و`multiedit`) +- `edit` — جميع تعديلات الملفات (يشمل `edit` و`write` و`patch`) - `glob` — مطابقة أسماء الملفات (يطابق نمط الـ glob) - `grep` — البحث في المحتوى (يطابق نمط regex) - `bash` — تشغيل أوامر shell (يطابق الأوامر المُحلَّلة مثل `git status --porcelain`) diff --git a/packages/web/src/content/docs/ar/tools.mdx b/packages/web/src/content/docs/ar/tools.mdx index 3f3c9ee06..0323e8d8e 100644 --- a/packages/web/src/content/docs/ar/tools.mdx +++ b/packages/web/src/content/docs/ar/tools.mdx @@ -95,7 +95,7 @@ description: إدارة الأدوات التي يمكن لـ LLM استخدام استخدم هذا للسماح لـ LLM بإنشاء ملفات جديدة. سيكتب فوق الملفات الموجودة إذا كانت موجودة بالفعل. :::note -تُدار أداة `write` عبر إذن `edit`، والذي يشمل جميع تعديلات الملفات (`edit` و`write` و`patch` و`multiedit`). +تُدار أداة `write` عبر إذن `edit`، والذي يشمل جميع تعديلات الملفات (`edit` و`write` و`patch`). ::: --- @@ -190,7 +190,7 @@ description: إدارة الأدوات التي يمكن لـ LLM استخدام تطبق هذه الأداة ملفات الرقع على قاعدة الشفرة الخاصة بك. وهي مفيدة لتطبيق الفروقات (Diffs) والرقع من مصادر متعددة. :::note -تُدار أداة `patch` عبر إذن `edit`، والذي يشمل جميع تعديلات الملفات (`edit` و`write` و`patch` و`multiedit`). +تُدار أداة `patch` عبر إذن `edit`، والذي يشمل جميع تعديلات الملفات (`edit` و`write` و`patch`). ::: --- diff --git a/packages/web/src/content/docs/bs/permissions.mdx b/packages/web/src/content/docs/bs/permissions.mdx index e27fa130b..ea8c3f00f 100644 --- a/packages/web/src/content/docs/bs/permissions.mdx +++ b/packages/web/src/content/docs/bs/permissions.mdx @@ -125,7 +125,7 @@ Držite ovu listu fokusiranom na pouzdane putanje, a dodatna allow/deny pravila Dozvole OpenCode su označene imenom alata, plus nekoliko sigurnosnih mjera: - `read` — čitanje datoteke (odgovara putanji datoteke) -- `edit` — sve izmjene fajlova (pokriva `edit`, `write`, `patch`, `multiedit`) +- `edit` — sve izmjene fajlova (pokriva `edit`, `write`, `patch`) - `glob` — globbiranje fajla (odgovara glob uzorku) - `grep` — pretraga sadržaja (podudara se sa regularnim izrazom) - `bash` — izvođenje komandi ljuske (podudara se s raščlanjenim komandama kao što je `git status --porcelain`) diff --git a/packages/web/src/content/docs/bs/tools.mdx b/packages/web/src/content/docs/bs/tools.mdx index db04295fd..c2d5aa2dd 100644 --- a/packages/web/src/content/docs/bs/tools.mdx +++ b/packages/web/src/content/docs/bs/tools.mdx @@ -95,7 +95,7 @@ Kreira nove datoteke ili prepisuje postojece. Koristite ovo da dozvolite LLM-u kreiranje novih datoteka. Ako datoteka vec postoji, bit ce prepisana. :::note -`write` alat kontrolise `edit` dozvola, koja pokriva sve izmjene datoteka (`edit`, `write`, `patch`, `multiedit`). +`write` alat kontrolise `edit` dozvola, koja pokriva sve izmjene datoteka (`edit`, `write`, `patch`). ::: --- @@ -190,7 +190,7 @@ Primjenjuje zakrpe na datoteke. Ovaj alat primjenjuje patch datoteke na kod. Koristan je za diffs i patch-eve iz razlicitih izvora. :::note -`patch` alat kontrolise `edit` dozvola, koja pokriva sve izmjene datoteka (`edit`, `write`, `patch`, `multiedit`). +`patch` alat kontrolise `edit` dozvola, koja pokriva sve izmjene datoteka (`edit`, `write`, `patch`). ::: --- diff --git a/packages/web/src/content/docs/da/permissions.mdx b/packages/web/src/content/docs/da/permissions.mdx index 176dd568e..30de7aa86 100644 --- a/packages/web/src/content/docs/da/permissions.mdx +++ b/packages/web/src/content/docs/da/permissions.mdx @@ -130,7 +130,7 @@ Hold listen fokuseret på betroede stier, og lag ekstra tillad eller afvis regle OpenCode tilladelser indtastes efter værktøjsnavn plus et par sikkerhedsafskærmninger: - `read` — læser en fil (matcher filstien) -- `edit` — alle filændringer (dækker `edit`, `write`, `patch`, `multiedit`) +- `edit` — alle filændringer (dækker `edit`, `write`, `patch`) - `glob` — fil-globing (matcher glob-mønsteret) - `grep` — indholdssøgning (matcher regex-mønsteret) - `bash` — kører shell-kommandoer (matcher parsede kommandoer som `git status --porcelain`) diff --git a/packages/web/src/content/docs/da/tools.mdx b/packages/web/src/content/docs/da/tools.mdx index 6f6f95c9c..31c980082 100644 --- a/packages/web/src/content/docs/da/tools.mdx +++ b/packages/web/src/content/docs/da/tools.mdx @@ -95,7 +95,7 @@ Opret nye filer eller overskriv eksisterende. Brug denne for at la LLM lage nye filer. Den vil overskrive eksisterende filer hvis de allerede eksisterer. :::note -`write`-verktøyet kontrolleres av tillatelsen `edit`, som dekker alle filendringer (`edit`, `write`, `patch`, `multiedit`). +`write`-verktøyet kontrolleres av tillatelsen `edit`, som dekker alle filendringer (`edit`, `write`, `patch`). ::: --- @@ -190,7 +190,7 @@ Brug patcher på filer. Dette verktøyet bruger opdateringsfiler til kodebasen din. Nyttig for at påføre diff og lapper fra forskjellige kilder. :::note -`patch`-verktøyet kontrolleres av tillatelsen `edit`, som dekker alle filendringer (`edit`, `write`, `patch`, `multiedit`). +`patch`-verktøyet kontrolleres av tillatelsen `edit`, som dekker alle filendringer (`edit`, `write`, `patch`). ::: --- diff --git a/packages/web/src/content/docs/de/permissions.mdx b/packages/web/src/content/docs/de/permissions.mdx index 6b647ca36..6769ae74d 100644 --- a/packages/web/src/content/docs/de/permissions.mdx +++ b/packages/web/src/content/docs/de/permissions.mdx @@ -130,7 +130,7 @@ Konzentrieren Sie sich in der Liste auf vertrauenswürdige Pfade und fügen Sie OpenCode-Berechtigungen basieren auf Tool-Namen sowie einigen Sicherheitsvorkehrungen: - `read` – eine Datei lesen (entspricht dem Dateipfad) -- `edit` – alle Dateiänderungen (umfasst `edit`, `write`, `patch`, `multiedit`) +- `edit` – alle Dateiänderungen (umfasst `edit`, `write`, `patch`) - `glob` – Datei-Globbing (entspricht dem Glob-Muster) - `grep` – Inhaltssuche (entspricht dem Regex-Muster) - `bash` – Ausführen von Shell-Befehlen (entspricht analysierten Befehlen wie `git status --porcelain`) diff --git a/packages/web/src/content/docs/de/tools.mdx b/packages/web/src/content/docs/de/tools.mdx index 6012148c6..8f5e7c071 100644 --- a/packages/web/src/content/docs/de/tools.mdx +++ b/packages/web/src/content/docs/de/tools.mdx @@ -102,7 +102,7 @@ Bestehende Dateien werden dabei ueberschrieben. :::note Das Tool `write` wird ueber die Berechtigung `edit` gesteuert. -`edit` gilt fuer alle Datei-Aenderungen (`edit`, `write`, `patch`, `multiedit`). +`edit` gilt fuer alle Datei-Aenderungen (`edit`, `write`, `patch`). ::: --- @@ -197,7 +197,7 @@ Wendet Patches auf Dateien an. Dieses Tool wendet Patch-Dateien auf deine Codebasis an. Nuetzlich fuer Diffs und Patches aus verschiedenen Quellen. :::note -Das Tool `patch` wird ueber die Berechtigung `edit` gesteuert, welche alle Datei-Aenderungen abdeckt (`edit`, `write`, `patch`, `multiedit`). +Das Tool `patch` wird ueber die Berechtigung `edit` gesteuert, welche alle Datei-Aenderungen abdeckt (`edit`, `write`, `patch`). ::: --- diff --git a/packages/web/src/content/docs/es/permissions.mdx b/packages/web/src/content/docs/es/permissions.mdx index 6923368e4..131ab323b 100644 --- a/packages/web/src/content/docs/es/permissions.mdx +++ b/packages/web/src/content/docs/es/permissions.mdx @@ -130,7 +130,7 @@ Mantenga la lista centrada en rutas confiables y aplique reglas adicionales de p Los permisos OpenCode están codificados por el nombre de la herramienta, además de un par de medidas de seguridad: - `read` — leer un archivo (coincide con la ruta del archivo) -- `edit` — todas las modificaciones de archivos (cubre `edit`, `write`, `patch`, `multiedit`) +- `edit` — todas las modificaciones de archivos (cubre `edit`, `write`, `patch`) - `glob` — globalización de archivos (coincide con el patrón global) - `grep` — búsqueda de contenido (coincide con el patrón de expresiones regulares) - `bash`: ejecuta comandos de shell (coincide con comandos analizados como `git status --porcelain`) diff --git a/packages/web/src/content/docs/es/tools.mdx b/packages/web/src/content/docs/es/tools.mdx index 83d61f532..5a0cca969 100644 --- a/packages/web/src/content/docs/es/tools.mdx +++ b/packages/web/src/content/docs/es/tools.mdx @@ -95,7 +95,7 @@ Cree nuevos archivos o sobrescriba los existentes. Utilice esto para permitir que LLM cree nuevos archivos. Sobrescribirá los archivos existentes si ya existen. :::note -La herramienta `write` está controlada por el permiso `edit`, que cubre todas las modificaciones de archivos (`edit`, `write`, `patch`, `multiedit`). +La herramienta `write` está controlada por el permiso `edit`, que cubre todas las modificaciones de archivos (`edit`, `write`, `patch`). ::: --- @@ -190,7 +190,7 @@ Aplicar parches a los archivos. Esta herramienta aplica archivos de parche a su código base. Útil para aplicar diferencias y parches de diversas fuentes. :::note -La herramienta `patch` está controlada por el permiso `edit`, que cubre todas las modificaciones de archivos (`edit`, `write`, `patch`, `multiedit`). +La herramienta `patch` está controlada por el permiso `edit`, que cubre todas las modificaciones de archivos (`edit`, `write`, `patch`). ::: --- diff --git a/packages/web/src/content/docs/fr/permissions.mdx b/packages/web/src/content/docs/fr/permissions.mdx index b1c1d6800..b3b9d7e2d 100644 --- a/packages/web/src/content/docs/fr/permissions.mdx +++ b/packages/web/src/content/docs/fr/permissions.mdx @@ -130,7 +130,7 @@ Gardez la liste centrée sur les chemins approuvés et superposez des règles d' Les autorisations OpenCode sont classées par nom d'outil, plus quelques garde-fous de sécurité : - `read` — lecture d'un fichier (correspond au chemin du fichier) -- `edit` — toutes les modifications de fichiers (couvre `edit`, `write`, `patch`, `multiedit`) +- `edit` — toutes les modifications de fichiers (couvre `edit`, `write`, `patch`) - `glob` — globalisation de fichiers (correspond au modèle global) - `grep` — recherche de contenu (correspond au modèle regex) - `bash` - exécution de commandes shell (correspond aux commandes analysées comme `git status --porcelain`) diff --git a/packages/web/src/content/docs/fr/tools.mdx b/packages/web/src/content/docs/fr/tools.mdx index 4f3f18046..72edf9819 100644 --- a/packages/web/src/content/docs/fr/tools.mdx +++ b/packages/web/src/content/docs/fr/tools.mdx @@ -95,7 +95,7 @@ Créez de nouveaux fichiers ou écrasez ceux existants. Utilisez-le pour permettre au LLM de créer de nouveaux fichiers. Il écrasera les fichiers existants s'ils existent déjà. :::note -L'outil `write` est contrôlé par l'autorisation `edit`, qui couvre toutes les modifications de fichiers (`edit`, `write`, `patch`, `multiedit`). +L'outil `write` est contrôlé par l'autorisation `edit`, qui couvre toutes les modifications de fichiers (`edit`, `write`, `patch`). ::: --- @@ -190,7 +190,7 @@ Appliquez des correctifs aux fichiers. Cet outil applique les fichiers de correctifs à votre base de code. Utile pour appliquer des différences et des correctifs provenant de diverses sources. :::note -L'outil `patch` est contrôlé par l'autorisation `edit`, qui couvre toutes les modifications de fichiers (`edit`, `write`, `patch`, `multiedit`). +L'outil `patch` est contrôlé par l'autorisation `edit`, qui couvre toutes les modifications de fichiers (`edit`, `write`, `patch`). ::: --- diff --git a/packages/web/src/content/docs/it/permissions.mdx b/packages/web/src/content/docs/it/permissions.mdx index 49f0e8e4d..417bf4a23 100644 --- a/packages/web/src/content/docs/it/permissions.mdx +++ b/packages/web/src/content/docs/it/permissions.mdx @@ -130,7 +130,7 @@ Mantieni l'elenco limitato a percorsi fidati e aggiungi regole extra di allow/de I permessi di OpenCode sono indicizzati per nome dello strumento, piu' un paio di guardrail di sicurezza: - `read` — lettura di un file (corrisponde al percorso del file) -- `edit` — tutte le modifiche ai file (include `edit`, `write`, `patch`, `multiedit`) +- `edit` — tutte le modifiche ai file (include `edit`, `write`, `patch`) - `glob` — ricerca file tramite glob (corrisponde al pattern glob) - `grep` — ricerca nel contenuto (corrisponde al pattern regex) - `bash` — esecuzione comandi di shell (corrisponde a comandi parsati come `git status --porcelain`) diff --git a/packages/web/src/content/docs/it/tools.mdx b/packages/web/src/content/docs/it/tools.mdx index c1e69f8be..e3f2f23ce 100644 --- a/packages/web/src/content/docs/it/tools.mdx +++ b/packages/web/src/content/docs/it/tools.mdx @@ -95,7 +95,7 @@ Crea nuovi file o sovrascrive quelli esistenti. Usalo per consentire all'LLM di creare nuovi file. Sovrascrivera' i file esistenti se sono gia' presenti. :::note -Lo strumento `write` e' controllato dal permesso `edit`, che copre tutte le modifiche ai file (`edit`, `write`, `patch`, `multiedit`). +Lo strumento `write` e' controllato dal permesso `edit`, che copre tutte le modifiche ai file (`edit`, `write`, `patch`). ::: --- @@ -190,7 +190,7 @@ Applica patch ai file. Questo strumento applica file patch al tuo codebase. Utile per applicare diff e patch da varie fonti. :::note -Lo strumento `patch` e' controllato dal permesso `edit`, che copre tutte le modifiche ai file (`edit`, `write`, `patch`, `multiedit`). +Lo strumento `patch` e' controllato dal permesso `edit`, che copre tutte le modifiche ai file (`edit`, `write`, `patch`). ::: --- diff --git a/packages/web/src/content/docs/ja/permissions.mdx b/packages/web/src/content/docs/ja/permissions.mdx index f2b097825..55f6f8700 100644 --- a/packages/web/src/content/docs/ja/permissions.mdx +++ b/packages/web/src/content/docs/ja/permissions.mdx @@ -130,7 +130,7 @@ OpenCode は `permission` 設定を使用して、特定のアクションを自 OpenCode の権限は、ツール名に加えて、いくつかの安全対策によってキー化されます。 - `read` — ファイルの読み取り (ファイルパスと一致) -- `edit` — すべてのファイル変更 (`edit`、`write`、`patch`、`multiedit` をカバー) +- `edit` — すべてのファイル変更 (`edit`、`write`、`patch` をカバー) - `glob` — ファイルのグロビング (グロブパターンと一致) - `grep` — コンテンツ検索 (正規表現パターンと一致) - `bash` — シェルコマンドの実行 (`git status --porcelain` などの解析されたコマンドと一致します) diff --git a/packages/web/src/content/docs/ja/tools.mdx b/packages/web/src/content/docs/ja/tools.mdx index 394506393..1473a0b36 100644 --- a/packages/web/src/content/docs/ja/tools.mdx +++ b/packages/web/src/content/docs/ja/tools.mdx @@ -95,7 +95,7 @@ OpenCode で利用可能なすべての組み込みツールを次に示しま これを使用して、LLM が新しいファイルを作成できるようにします。既存のファイルがすでに存在する場合は上書きされます。 :::note -`write` ツールは、すべてのファイル変更 (`edit`、`edit`、`write`、`patch`) をカバーする `multiedit` 権限によって制御されます。 +`write` ツールは、すべてのファイル変更 (`edit`、`write`、`patch`) をカバーする `edit` 権限によって制御されます。 ::: --- @@ -190,7 +190,7 @@ OpenCode で利用可能なすべての組み込みツールを次に示しま このツールは、コードベースにパッチファイルを適用します。さまざまなソースからの差分やパッチを適用するのに役立ちます。 :::note -`patch` ツールは、すべてのファイル変更 (`edit`、`edit`、`write`、`patch`) をカバーする `multiedit` 権限によって制御されます。 +`patch` ツールは、すべてのファイル変更 (`edit`、`write`、`patch`) をカバーする `edit` 権限によって制御されます。 ::: --- diff --git a/packages/web/src/content/docs/ko/permissions.mdx b/packages/web/src/content/docs/ko/permissions.mdx index 0742089d6..e19c3c49c 100644 --- a/packages/web/src/content/docs/ko/permissions.mdx +++ b/packages/web/src/content/docs/ko/permissions.mdx @@ -130,7 +130,7 @@ Permission 본 사용 간단한 wildcard 일치: opencode 권한은 도구 이름에 의해 키 입력되며, 두 개의 안전 가드 : - `read` - 파일 읽기 (파일 경로의 매칭) -- `edit` - 모든 파일 수정 (covers `edit`, `write`, `patch`, `multiedit`) +- `edit` - 모든 파일 수정 (covers `edit`, `write`, `patch`) - `glob` - 파일 globbing (glob 패턴 매칭) - `grep` - 콘텐츠 검색 ( regex 패턴 매칭) - `bash` - shell 명령 실행 (`git status --porcelain`와 같은 팟 명령) diff --git a/packages/web/src/content/docs/ko/tools.mdx b/packages/web/src/content/docs/ko/tools.mdx index 49bea93cb..8fe5ab1d0 100644 --- a/packages/web/src/content/docs/ko/tools.mdx +++ b/packages/web/src/content/docs/ko/tools.mdx @@ -95,7 +95,7 @@ description: LLM이 사용할 수 있는 도구를 관리합니다. LLM을 사용하여 새 파일을 만듭니다. 이미 존재하는 경우 기존 파일을 덮어쓰겠습니다. :::note -`write` 공구는 모든 파일 수정 (`edit`, `write`, `patch`, `multiedit`)를 포함하는 `edit` 허가에 의해 통제됩니다. +`write` 공구는 모든 파일 수정 (`edit`, `write`, `patch`)를 포함하는 `edit` 허가에 의해 통제됩니다. ::: --- @@ -190,7 +190,7 @@ LSP 서버가 프로젝트에 사용할 수 있는 구성하려면 [LSP Servers] 이 도구는 코드베이스에 패치 파일을 적용합니다. 다양한 소스에서 diffs 및 Patch를 적용하는 데 유용합니다. :::note -`patch` 공구는 모든 파일 수정 (`edit`, `write`, `patch`, `multiedit`)를 포함하는 `edit` 허가에 의해 통제됩니다. +`patch` 공구는 모든 파일 수정 (`edit`, `write`, `patch`)를 포함하는 `edit` 허가에 의해 통제됩니다. ::: --- diff --git a/packages/web/src/content/docs/nb/permissions.mdx b/packages/web/src/content/docs/nb/permissions.mdx index 5c63b251e..5f54d23aa 100644 --- a/packages/web/src/content/docs/nb/permissions.mdx +++ b/packages/web/src/content/docs/nb/permissions.mdx @@ -130,7 +130,7 @@ Hold listen fokusert på klarerte baner, og lag ekstra tillat eller avslå regle OpenCode-tillatelser tastes inn etter verktøynavn, pluss et par sikkerhetsvakter: - `read` — lesing av en fil (tilsvarer filbanen) -- `edit` — alle filendringer (dekker `edit`, `write`, `patch`, `multiedit`) +- `edit` — alle filendringer (dekker `edit`, `write`, `patch`) - `glob` — fil-globing (tilsvarer glob-mønsteret) - `grep` — innholdssøk (samsvarer med regex-mønsteret) - `bash` — kjører skallkommandoer (matcher analyserte kommandoer som `git status --porcelain`) diff --git a/packages/web/src/content/docs/nb/tools.mdx b/packages/web/src/content/docs/nb/tools.mdx index 8c871f11c..2e6f61ba0 100644 --- a/packages/web/src/content/docs/nb/tools.mdx +++ b/packages/web/src/content/docs/nb/tools.mdx @@ -95,7 +95,7 @@ Opprett nye filer eller overskriv eksisterende. Bruk denne for å la LLM lage nye filer. Den vil overskrive eksisterende filer hvis de allerede eksisterer. :::note -`write`-verktøyet kontrolleres av tillatelsen `edit`, som dekker alle filendringer (`edit`, `write`, `patch`, `multiedit`). +`write`-verktøyet kontrolleres av tillatelsen `edit`, som dekker alle filendringer (`edit`, `write`, `patch`). ::: --- @@ -190,7 +190,7 @@ Bruk patcher på filer. Dette verktøyet bruker oppdateringsfiler til kodebasen din. Nyttig for å påføre diffs og patcher fra forskjellige kilder. :::note -`patch`-verktøyet kontrolleres av tillatelsen `edit`, som dekker alle filendringer (`edit`, `write`, `patch`, `multiedit`). +`patch`-verktøyet kontrolleres av tillatelsen `edit`, som dekker alle filendringer (`edit`, `write`, `patch`). ::: --- diff --git a/packages/web/src/content/docs/permissions.mdx b/packages/web/src/content/docs/permissions.mdx index 6383b2a3f..f2efe3a49 100644 --- a/packages/web/src/content/docs/permissions.mdx +++ b/packages/web/src/content/docs/permissions.mdx @@ -130,7 +130,7 @@ Keep the list focused on trusted paths, and layer extra allow or deny rules as n OpenCode permissions are keyed by tool name, plus a couple of safety guards: - `read` — reading a file (matches the file path) -- `edit` — all file modifications (covers `edit`, `write`, `patch`, `multiedit`) +- `edit` — all file modifications (covers `edit`, `write`, `patch`) - `glob` — file globbing (matches the glob pattern) - `grep` — content search (matches the regex pattern) - `bash` — running shell commands (matches parsed commands like `git status --porcelain`) diff --git a/packages/web/src/content/docs/pl/permissions.mdx b/packages/web/src/content/docs/pl/permissions.mdx index a5c05b6dc..b17b87347 100644 --- a/packages/web/src/content/docs/pl/permissions.mdx +++ b/packages/web/src/content/docs/pl/permissions.mdx @@ -130,7 +130,7 @@ Skoncentruj listę na zaufanych ścieżkach i dodaj dodatkowe zezwolenie lub odm Uprawnienia opencode są określane na podstawie nazwy narzędzia i kilku zabezpieczeń: - `read` — odczyt pliku (odpowiada ścieżce pliku) -- `edit` — wszystkie modyfikacje plików (obejmuje `edit`, `write`, `patch`, `multiedit`) +- `edit` — wszystkie modyfikacje plików (obejmuje `edit`, `write`, `patch`) - `glob` — maglowanie plików (pasuje do wzorców globowania) - `grep` — wyszukiwanie treści (pasuje do wzorca regularnego) - `bash` — uruchamianie poleceń shell (pasuje do poleceń przeanalizowanych, takich jak `git status --porcelain`) diff --git a/packages/web/src/content/docs/pl/tools.mdx b/packages/web/src/content/docs/pl/tools.mdx index 180e043cd..6b8a5eeb4 100644 --- a/packages/web/src/content/docs/pl/tools.mdx +++ b/packages/web/src/content/docs/pl/tools.mdx @@ -95,7 +95,7 @@ Twórz nowe pliki lub nadpisuj istniejące. Użyj tego, aby umożliwić LLM tworzenie nowych plików. Zastąpi istniejące pliki, jeśli już istnieją. :::note -Narzędzie `write` jest kontrolowane przez uprawnienie `edit`, które obejmuje wszystkie modyfikacje plików (`edit`, `write`, `patch`, `multiedit`). +Narzędzie `write` jest kontrolowane przez uprawnienie `edit`, które obejmuje wszystkie modyfikacje plików (`edit`, `write`, `patch`). ::: --- @@ -190,7 +190,7 @@ Stosuj łatki (patches) do plików. To narzędzie stosuje pliki różnicowe (diffs) do bazy kodu. Przydatne do stosowania zmian z różnych źródeł. :::note -Narzędzie `patch` jest kontrolowane przez uprawnienie `edit`, które obejmuje wszystkie modyfikacje plików (`edit`, `write`, `patch`, `multiedit`). +Narzędzie `patch` jest kontrolowane przez uprawnienie `edit`, które obejmuje wszystkie modyfikacje plików (`edit`, `write`, `patch`). ::: --- diff --git a/packages/web/src/content/docs/pt-br/permissions.mdx b/packages/web/src/content/docs/pt-br/permissions.mdx index 4facc9f72..077524b87 100644 --- a/packages/web/src/content/docs/pt-br/permissions.mdx +++ b/packages/web/src/content/docs/pt-br/permissions.mdx @@ -130,7 +130,7 @@ Mantenha a lista focada em caminhos confiáveis e adicione regras adicionais de As permissões do opencode são indexadas pelo nome da ferramenta, além de alguns guardas de segurança: - `read` — leitura de um arquivo (corresponde ao caminho do arquivo) -- `edit` — todas as modificações de arquivo (cobre `edit`, `write`, `patch`, `multiedit`) +- `edit` — todas as modificações de arquivo (cobre `edit`, `write`, `patch`) - `glob` — globbing de arquivos (corresponde ao padrão glob) - `grep` — busca de conteúdo (corresponde ao padrão regex) - `bash` — execução de comandos de shell (corresponde a comandos analisados como `git status --porcelain`) diff --git a/packages/web/src/content/docs/pt-br/tools.mdx b/packages/web/src/content/docs/pt-br/tools.mdx index 4c7b37197..86fd714d3 100644 --- a/packages/web/src/content/docs/pt-br/tools.mdx +++ b/packages/web/src/content/docs/pt-br/tools.mdx @@ -95,7 +95,7 @@ Crie novos arquivos ou sobrescreva os existentes. Use isso para permitir que o LLM crie novos arquivos. Ele sobrescreverá arquivos existentes se já existirem. :::note -A ferramenta `write` é controlada pela permissão `edit`, que cobre todas as modificações de arquivos (`edit`, `write`, `patch`, `multiedit`). +A ferramenta `write` é controlada pela permissão `edit`, que cobre todas as modificações de arquivos (`edit`, `write`, `patch`). ::: --- @@ -190,7 +190,7 @@ Aplique patches a arquivos. Esta ferramenta aplica arquivos de patch à sua base de código. Útil para aplicar diffs e patches de várias fontes. :::note -A ferramenta `patch` é controlada pela permissão `edit`, que cobre todas as modificações de arquivos (`edit`, `write`, `patch`, `multiedit`). +A ferramenta `patch` é controlada pela permissão `edit`, que cobre todas as modificações de arquivos (`edit`, `write`, `patch`). ::: --- diff --git a/packages/web/src/content/docs/ru/permissions.mdx b/packages/web/src/content/docs/ru/permissions.mdx index 961a06824..7e027029e 100644 --- a/packages/web/src/content/docs/ru/permissions.mdx +++ b/packages/web/src/content/docs/ru/permissions.mdx @@ -130,7 +130,7 @@ opencode использует конфигурацию `permission`, чтобы Разрешения opencode привязаны к имени инструмента, а также к нескольким мерам безопасности: - `read` — чтение файла (соответствует пути к файлу) -- `edit` — все модификации файлов (охватывает `edit`, `write`, `patch`, `multiedit`) +- `edit` — все модификации файлов (охватывает `edit`, `write`, `patch`) - `glob` — подстановка файла (соответствует шаблону подстановки) - `grep` — поиск по контенту (соответствует шаблону регулярного выражения) - `bash` — запуск shell-команд (соответствует проанализированным командам, например `git status --porcelain`) diff --git a/packages/web/src/content/docs/ru/tools.mdx b/packages/web/src/content/docs/ru/tools.mdx index 35958e036..b34d30465 100644 --- a/packages/web/src/content/docs/ru/tools.mdx +++ b/packages/web/src/content/docs/ru/tools.mdx @@ -95,7 +95,7 @@ description: Управляйте инструментами, которые м Используйте это, чтобы позволить LLM создавать новые файлы. Он перезапишет существующие файлы, если они уже существуют. :::note -Инструмент `write` контролируется разрешением `edit`, которое распространяется на все модификации файлов (`edit`, `write`, `patch`, `multiedit`). +Инструмент `write` контролируется разрешением `edit`, которое распространяется на все модификации файлов (`edit`, `write`, `patch`). ::: --- @@ -190,7 +190,7 @@ description: Управляйте инструментами, которые м Этот инструмент применяет файлы исправлений к вашей кодовой базе. Полезно для применения различий и патчей из различных источников. :::note -Инструмент `patch` контролируется разрешением `edit`, которое распространяется на все модификации файлов (`edit`, `write`, `patch`, `multiedit`). +Инструмент `patch` контролируется разрешением `edit`, которое распространяется на все модификации файлов (`edit`, `write`, `patch`). ::: --- diff --git a/packages/web/src/content/docs/th/permissions.mdx b/packages/web/src/content/docs/th/permissions.mdx index 5fed61615..4526b7495 100644 --- a/packages/web/src/content/docs/th/permissions.mdx +++ b/packages/web/src/content/docs/th/permissions.mdx @@ -130,7 +130,7 @@ OpenCode ใช้การกำหนดค่า `permission` เพื่อ สิทธิ์ของ OpenCode จะกำหนดไว้ตามชื่อเครื่องมือ พร้อมด้วย guardrails อีก 2-3 คน: - `read` — อ่านไฟล์ (ตรงกับเส้นทางของไฟล์) -- `edit` — การแก้ไขไฟล์ทั้งหมด (ครอบคลุมถึง `edit`, `write`, `patch`, `multiedit`) +- `edit` — การแก้ไขไฟล์ทั้งหมด (ครอบคลุมถึง `edit`, `write`, `patch`) - `glob` — ไฟล์ globbing (ตรงกับรูปแบบ glob) - `grep` — การค้นหาเนื้อหา (ตรงกับรูปแบบ regex) - `bash` — การรันคำสั่ง shell (ตรงกับคำสั่งที่แยกวิเคราะห์เช่น `git status --porcelain`) diff --git a/packages/web/src/content/docs/th/tools.mdx b/packages/web/src/content/docs/th/tools.mdx index 0ead63846..5006b41de 100644 --- a/packages/web/src/content/docs/th/tools.mdx +++ b/packages/web/src/content/docs/th/tools.mdx @@ -95,7 +95,7 @@ description: จัดการเครื่องมือที่ LLM ส ใช้สิ่งนี้เพื่ออนุญาตให้ LLM สร้างไฟล์ใหม่ มันจะเขียนทับไฟล์ที่มีอยู่หากมีอยู่แล้ว :::note -เครื่องมือ `write` ถูกควบคุมโดยสิทธิ์ `edit` ซึ่งครอบคลุมการแก้ไขไฟล์ทั้งหมด (`edit`, `write`, `patch`, `multiedit`) +เครื่องมือ `write` ถูกควบคุมโดยสิทธิ์ `edit` ซึ่งครอบคลุมการแก้ไขไฟล์ทั้งหมด (`edit`, `write`, `patch`) ::: --- @@ -190,7 +190,7 @@ description: จัดการเครื่องมือที่ LLM ส เครื่องมือนี้ใช้ไฟล์แพทช์กับโค้ดเบสของคุณ มีประโยชน์สำหรับการใช้ความแตกต่างและแพตช์จากแหล่งต่างๆ :::note -เครื่องมือ `patch` ถูกควบคุมโดยสิทธิ์ `edit` ซึ่งครอบคลุมการแก้ไขไฟล์ทั้งหมด (`edit`, `write`, `patch`, `multiedit`) +เครื่องมือ `patch` ถูกควบคุมโดยสิทธิ์ `edit` ซึ่งครอบคลุมการแก้ไขไฟล์ทั้งหมด (`edit`, `write`, `patch`) ::: --- diff --git a/packages/web/src/content/docs/tools.mdx b/packages/web/src/content/docs/tools.mdx index f05e980b8..e8d5e0963 100644 --- a/packages/web/src/content/docs/tools.mdx +++ b/packages/web/src/content/docs/tools.mdx @@ -95,7 +95,7 @@ Create new files or overwrite existing ones. Use this to allow the LLM to create new files. It will overwrite existing files if they already exist. :::note -The `write` tool is controlled by the `edit` permission, which covers all file modifications (`edit`, `write`, `apply_patch`, `multiedit`). +The `write` tool is controlled by the `edit` permission, which covers all file modifications (`edit`, `write`, `apply_patch`). ::: --- @@ -194,7 +194,7 @@ When handling `tool.execute.before` or `tool.execute.after` hooks, check `input. `apply_patch` uses `output.args.patchText` instead of `output.args.filePath`. Paths are embedded in marker lines within `patchText` and are relative to the project root (for example: `*** Add File: src/new-file.ts`, `*** Update File: src/existing.ts`, `*** Move to: src/renamed.ts`, `*** Delete File: src/obsolete.ts`). :::note -The `apply_patch` tool is controlled by the `edit` permission, which covers all file modifications (`edit`, `write`, `apply_patch`, `multiedit`). +The `apply_patch` tool is controlled by the `edit` permission, which covers all file modifications (`edit`, `write`, `apply_patch`). ::: --- diff --git a/packages/web/src/content/docs/tr/permissions.mdx b/packages/web/src/content/docs/tr/permissions.mdx index 976ee0a7f..89c0ebb2c 100644 --- a/packages/web/src/content/docs/tr/permissions.mdx +++ b/packages/web/src/content/docs/tr/permissions.mdx @@ -130,7 +130,7 @@ Listeyi güvenilir yollara odaklı tutun ve diğer araçlar için gereken ekstra opencode izinleri araç adına ve birkaç güvenlik önlemine göre anahtarlanır: - `read` — bir dosyayı okumak (dosya yoluyla eşleşir) -- `edit` — tüm dosya değişiklikleri (`edit`, `write`, `patch`, `multiedit`'yi kapsar) +- `edit` — tüm dosya değişiklikleri (`edit`, `write`, `patch`'i kapsar) - `glob` — dosya genellemesi (glob düzeniyle eşleşir) - `grep` — içerik arama (regex modeliyle eşleşir) - `bash` — kabuk komutlarını çalıştırma (`git status --porcelain` gibi ayrıştırılmış komutlarla eşleşir) diff --git a/packages/web/src/content/docs/tr/tools.mdx b/packages/web/src/content/docs/tr/tools.mdx index 2beb19009..29c1e0054 100644 --- a/packages/web/src/content/docs/tr/tools.mdx +++ b/packages/web/src/content/docs/tr/tools.mdx @@ -95,7 +95,7 @@ Yeni dosyalar oluşturur veya mevcut dosyaları üzerine yazar. LLM'in yeni dosya oluşturmasına izin vermek için bunu kullanın. Dosya zaten varsa üzerine yazar. :::note -`write` aracı `edit` izniyle kontrol edilir; bu izin tüm dosya değişikliklerini kapsar (`edit`, `write`, `patch`, `multiedit`). +`write` aracı `edit` izniyle kontrol edilir; bu izin tüm dosya değişikliklerini kapsar (`edit`, `write`, `patch`). ::: --- @@ -190,7 +190,7 @@ Dosyalara patch uygular. Bu araç patch dosyalarını kod tabanınıza uygular. Farklı kaynaklardan gelen diff ve patch'leri uygulamak için kullanışlıdır. :::note -`patch` aracı `edit` izniyle kontrol edilir; bu izin tüm dosya değişikliklerini kapsar (`edit`, `write`, `patch`, `multiedit`). +`patch` aracı `edit` izniyle kontrol edilir; bu izin tüm dosya değişikliklerini kapsar (`edit`, `write`, `patch`). ::: --- diff --git a/packages/web/src/content/docs/zh-cn/permissions.mdx b/packages/web/src/content/docs/zh-cn/permissions.mdx index f928554f2..a497742c0 100644 --- a/packages/web/src/content/docs/zh-cn/permissions.mdx +++ b/packages/web/src/content/docs/zh-cn/permissions.mdx @@ -130,7 +130,7 @@ OpenCode 使用 `permission` 配置来决定某个操作是否应自动运行、 OpenCode 的权限以工具名称为键,外加几个安全防护项: - `read` — 读取文件(匹配文件路径) -- `edit` — 所有文件修改(涵盖 `edit`、`write`、`patch`、`multiedit`) +- `edit` — 所有文件修改(涵盖 `edit`、`write`、`patch`) - `glob` — 文件通配(匹配通配模式) - `grep` — 内容搜索(匹配正则表达式模式) - `bash` — 运行 shell 命令(匹配解析后的命令,如 `git status --porcelain`) diff --git a/packages/web/src/content/docs/zh-cn/tools.mdx b/packages/web/src/content/docs/zh-cn/tools.mdx index 4c6037059..1a58eece5 100644 --- a/packages/web/src/content/docs/zh-cn/tools.mdx +++ b/packages/web/src/content/docs/zh-cn/tools.mdx @@ -95,7 +95,7 @@ description: 管理 LLM 可以使用的工具。 使用此工具允许 LLM 创建新文件。如果文件已存在,则会覆盖现有文件。 :::note -`write` 工具由 `edit` 权限控制,该权限涵盖所有文件修改操作(`edit`、`write`、`patch`、`multiedit`)。 +`write` 工具由 `edit` 权限控制,该权限涵盖所有文件修改操作(`edit`、`write`、`patch`)。 ::: --- @@ -190,7 +190,7 @@ description: 管理 LLM 可以使用的工具。 该工具将补丁文件应用到您的代码库中。适用于应用来自各种来源的 diff 和补丁。 :::note -`patch` 工具由 `edit` 权限控制,该权限涵盖所有文件修改操作(`edit`、`write`、`patch`、`multiedit`)。 +`patch` 工具由 `edit` 权限控制,该权限涵盖所有文件修改操作(`edit`、`write`、`patch`)。 ::: --- diff --git a/packages/web/src/content/docs/zh-tw/permissions.mdx b/packages/web/src/content/docs/zh-tw/permissions.mdx index bacd87c1e..5d98bddfb 100644 --- a/packages/web/src/content/docs/zh-tw/permissions.mdx +++ b/packages/web/src/content/docs/zh-tw/permissions.mdx @@ -130,7 +130,7 @@ OpenCode 使用 `permission` 設定來決定某個操作是否應自動執行、 OpenCode 的權限以工具名稱為鍵,外加幾個安全防護項: - `read` — 讀取檔案(比對檔案路徑) -- `edit` — 所有檔案修改(涵蓋 `edit`、`write`、`patch`、`multiedit`) +- `edit` — 所有檔案修改(涵蓋 `edit`、`write`、`patch`) - `glob` — 檔案萬用字元比對(比對萬用字元模式) - `grep` — 內容搜尋(比對正規表示式模式) - `bash` — 執行 shell 指令(比對解析後的指令,如 `git status --porcelain`) diff --git a/packages/web/src/content/docs/zh-tw/tools.mdx b/packages/web/src/content/docs/zh-tw/tools.mdx index 6ce68d9fb..763d0ce9a 100644 --- a/packages/web/src/content/docs/zh-tw/tools.mdx +++ b/packages/web/src/content/docs/zh-tw/tools.mdx @@ -95,7 +95,7 @@ description: 管理 LLM 可以使用的工具。 使用此工具允許 LLM 建立新檔案。如果檔案已存在,則會覆蓋現有檔案。 :::note -`write` 工具由 `edit` 權限控制,該權限涵蓋所有檔案修改操作(`edit`、`write`、`patch`、`multiedit`)。 +`write` 工具由 `edit` 權限控制,該權限涵蓋所有檔案修改操作(`edit`、`write`、`patch`)。 ::: --- @@ -190,7 +190,7 @@ description: 管理 LLM 可以使用的工具。 該工具將補丁檔案套用到您的程式碼庫中。適用於套用來自各種來源的 diff 和補丁。 :::note -`patch` 工具由 `edit` 權限控制,該權限涵蓋所有檔案修改操作(`edit`、`write`、`patch`、`multiedit`)。 +`patch` 工具由 `edit` 權限控制,該權限涵蓋所有檔案修改操作(`edit`、`write`、`patch`)。 ::: --- From 95794292762caff2700128388e2672e4a5f5ab07 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 11:54:40 -0400 Subject: [PATCH 018/426] test(opencode): consolidate session prompt tests into Effect style (#23710) --- .../test/session/prompt-effect.test.ts | 1522 ----------- packages/opencode/test/session/prompt.test.ts | 2324 +++++++++++++---- 2 files changed, 1810 insertions(+), 2036 deletions(-) delete mode 100644 packages/opencode/test/session/prompt-effect.test.ts diff --git a/packages/opencode/test/session/prompt-effect.test.ts b/packages/opencode/test/session/prompt-effect.test.ts deleted file mode 100644 index 2f5904684..000000000 --- a/packages/opencode/test/session/prompt-effect.test.ts +++ /dev/null @@ -1,1522 +0,0 @@ -import { NodeFileSystem } from "@effect/platform-node" -import { FetchHttpClient } from "effect/unstable/http" -import { expect } from "bun:test" -import { Cause, Effect, Exit, Fiber, Layer } from "effect" -import path from "path" -import { Agent as AgentSvc } from "../../src/agent/agent" -import { Bus } from "../../src/bus" -import { Command } from "../../src/command" -import { Config } from "../../src/config" -import { LSP } from "../../src/lsp" -import { MCP } from "../../src/mcp" -import { Permission } from "../../src/permission" -import { Plugin } from "../../src/plugin" -import { Provider as ProviderSvc } from "../../src/provider" -import { Env } from "../../src/env" -import { ModelID, ProviderID } from "../../src/provider/schema" -import { Question } from "../../src/question" -import { Todo } from "../../src/session/todo" -import { Session } from "../../src/session" -import { LLM } from "../../src/session/llm" -import { MessageV2 } from "../../src/session/message-v2" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { SessionCompaction } from "../../src/session/compaction" -import { SessionSummary } from "../../src/session/summary" -import { Instruction } from "../../src/session/instruction" -import { SessionProcessor } from "../../src/session/processor" -import { SessionPrompt } from "../../src/session/prompt" -import { SessionRevert } from "../../src/session/revert" -import { SessionRunState } from "../../src/session/run-state" -import { MessageID, PartID, SessionID } from "../../src/session/schema" -import { SessionStatus } from "../../src/session/status" -import { Skill } from "../../src/skill" -import { SystemPrompt } from "../../src/session/system" -import { Shell } from "../../src/shell/shell" -import { Snapshot } from "../../src/snapshot" -import { ToolRegistry } from "../../src/tool" -import { Truncate } from "../../src/tool" -import { Log } from "../../src/util" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" -import { Ripgrep } from "../../src/file/ripgrep" -import { Format } from "../../src/format" -import { provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture" -import { testEffect } from "../lib/effect" -import { reply, TestLLMServer } from "../lib/llm-server" - -void Log.init({ print: false }) - -const summary = Layer.succeed( - SessionSummary.Service, - SessionSummary.Service.of({ - summarize: () => Effect.void, - diff: () => Effect.succeed([]), - computeDiff: () => Effect.succeed([]), - }), -) - -const ref = { - providerID: ProviderID.make("test"), - modelID: ModelID.make("test-model"), -} - -function defer() { - let resolve!: (value: T | PromiseLike) => void - const promise = new Promise((done) => { - resolve = done - }) - return { promise, resolve } -} - -function withSh(fx: () => Effect.Effect) { - return Effect.acquireUseRelease( - Effect.sync(() => { - const prev = process.env.SHELL - process.env.SHELL = "/bin/sh" - Shell.preferred.reset() - return prev - }), - () => fx(), - (prev) => - Effect.sync(() => { - if (prev === undefined) delete process.env.SHELL - else process.env.SHELL = prev - Shell.preferred.reset() - }), - ) -} - -function toolPart(parts: MessageV2.Part[]) { - return parts.find((part): part is MessageV2.ToolPart => part.type === "tool") -} - -type CompletedToolPart = MessageV2.ToolPart & { state: MessageV2.ToolStateCompleted } -type ErrorToolPart = MessageV2.ToolPart & { state: MessageV2.ToolStateError } - -function completedTool(parts: MessageV2.Part[]) { - const part = toolPart(parts) - expect(part?.state.status).toBe("completed") - return part?.state.status === "completed" ? (part as CompletedToolPart) : undefined -} - -function errorTool(parts: MessageV2.Part[]) { - const part = toolPart(parts) - expect(part?.state.status).toBe("error") - return part?.state.status === "error" ? (part as ErrorToolPart) : undefined -} - -const mcp = Layer.succeed( - MCP.Service, - MCP.Service.of({ - status: () => Effect.succeed({}), - clients: () => Effect.succeed({}), - tools: () => Effect.succeed({}), - prompts: () => Effect.succeed({}), - resources: () => Effect.succeed({}), - add: () => Effect.succeed({ status: { status: "disabled" as const } }), - connect: () => Effect.void, - disconnect: () => Effect.void, - getPrompt: () => Effect.succeed(undefined), - readResource: () => Effect.succeed(undefined), - startAuth: () => Effect.die("unexpected MCP auth in prompt-effect tests"), - authenticate: () => Effect.die("unexpected MCP auth in prompt-effect tests"), - finishAuth: () => Effect.die("unexpected MCP auth in prompt-effect tests"), - removeAuth: () => Effect.void, - supportsOAuth: () => Effect.succeed(false), - hasStoredTokens: () => Effect.succeed(false), - getAuthStatus: () => Effect.succeed("not_authenticated" as const), - }), -) - -const lsp = Layer.succeed( - LSP.Service, - LSP.Service.of({ - init: () => Effect.void, - status: () => Effect.succeed([]), - hasClients: () => Effect.succeed(false), - touchFile: () => Effect.void, - diagnostics: () => Effect.succeed({}), - hover: () => Effect.succeed(undefined), - definition: () => Effect.succeed([]), - references: () => Effect.succeed([]), - implementation: () => Effect.succeed([]), - documentSymbol: () => Effect.succeed([]), - workspaceSymbol: () => Effect.succeed([]), - prepareCallHierarchy: () => Effect.succeed([]), - incomingCalls: () => Effect.succeed([]), - outgoingCalls: () => Effect.succeed([]), - }), -) - -const status = SessionStatus.layer.pipe(Layer.provideMerge(Bus.layer)) -const run = SessionRunState.layer.pipe(Layer.provide(status)) -const infra = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer) -function makeHttp() { - const deps = Layer.mergeAll( - Session.defaultLayer, - Snapshot.defaultLayer, - LLM.defaultLayer, - Env.defaultLayer, - AgentSvc.defaultLayer, - Command.defaultLayer, - Permission.defaultLayer, - Plugin.defaultLayer, - Config.defaultLayer, - ProviderSvc.defaultLayer, - lsp, - mcp, - AppFileSystem.defaultLayer, - status, - ).pipe(Layer.provideMerge(infra)) - const question = Question.layer.pipe(Layer.provideMerge(deps)) - const todo = Todo.layer.pipe(Layer.provideMerge(deps)) - const registry = ToolRegistry.layer.pipe( - Layer.provide(Skill.defaultLayer), - Layer.provide(FetchHttpClient.layer), - Layer.provide(CrossSpawnSpawner.defaultLayer), - Layer.provide(Ripgrep.defaultLayer), - Layer.provide(Format.defaultLayer), - Layer.provideMerge(todo), - Layer.provideMerge(question), - Layer.provideMerge(deps), - ) - const trunc = Truncate.layer.pipe(Layer.provideMerge(deps)) - const proc = SessionProcessor.layer.pipe(Layer.provide(summary), Layer.provideMerge(deps)) - const compact = SessionCompaction.layer.pipe(Layer.provideMerge(proc), Layer.provideMerge(deps)) - return Layer.mergeAll( - TestLLMServer.layer, - SessionPrompt.layer.pipe( - Layer.provide(SessionRevert.defaultLayer), - Layer.provide(summary), - Layer.provideMerge(run), - Layer.provideMerge(compact), - Layer.provideMerge(proc), - Layer.provideMerge(registry), - Layer.provideMerge(trunc), - Layer.provide(Instruction.defaultLayer), - Layer.provide(SystemPrompt.defaultLayer), - Layer.provideMerge(deps), - ), - ).pipe(Layer.provide(summary)) -} - -const it = testEffect(makeHttp()) -const unix = process.platform !== "win32" ? it.live : it.live.skip - -// Config that registers a custom "test" provider with a "test-model" model -// so provider model lookup succeeds inside the loop. -const cfg = { - provider: { - test: { - name: "Test", - id: "test", - env: [], - npm: "@ai-sdk/openai-compatible", - models: { - "test-model": { - id: "test-model", - name: "Test Model", - attachment: false, - reasoning: false, - temperature: false, - tool_call: true, - release_date: "2025-01-01", - limit: { context: 100000, output: 10000 }, - cost: { input: 0, output: 0 }, - options: {}, - }, - }, - options: { - apiKey: "test-key", - baseURL: "http://localhost:1/v1", - }, - }, - }, -} - -function providerCfg(url: string) { - return { - ...cfg, - provider: { - ...cfg.provider, - test: { - ...cfg.provider.test, - options: { - ...cfg.provider.test.options, - baseURL: url, - }, - }, - }, - } -} - -const user = Effect.fn("test.user")(function* (sessionID: SessionID, text: string) { - const session = yield* Session.Service - const msg = yield* session.updateMessage({ - id: MessageID.ascending(), - role: "user", - sessionID, - agent: "build", - model: ref, - time: { created: Date.now() }, - }) - yield* session.updatePart({ - id: PartID.ascending(), - messageID: msg.id, - sessionID, - type: "text", - text, - }) - return msg -}) - -const seed = Effect.fn("test.seed")(function* (sessionID: SessionID, opts?: { finish?: string }) { - const session = yield* Session.Service - const msg = yield* user(sessionID, "hello") - const assistant: MessageV2.Assistant = { - id: MessageID.ascending(), - role: "assistant", - parentID: msg.id, - sessionID, - mode: "build", - agent: "build", - cost: 0, - path: { cwd: "/tmp", root: "/tmp" }, - tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, - modelID: ref.modelID, - providerID: ref.providerID, - time: { created: Date.now() }, - ...(opts?.finish ? { finish: opts.finish } : {}), - } - yield* session.updateMessage(assistant) - yield* session.updatePart({ - id: PartID.ascending(), - messageID: assistant.id, - sessionID, - type: "text", - text: "hi there", - }) - return { user: msg, assistant } -}) - -const addSubtask = (sessionID: SessionID, messageID: MessageID, model = ref) => - Effect.gen(function* () { - const session = yield* Session.Service - yield* session.updatePart({ - id: PartID.ascending(), - messageID, - sessionID, - type: "subtask", - prompt: "look into the cache key path", - description: "inspect bug", - agent: "general", - model, - }) - }) - -const boot = Effect.fn("test.boot")(function* (input?: { title?: string }) { - const prompt = yield* SessionPrompt.Service - const run = yield* SessionRunState.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create(input ?? { title: "Pinned" }) - return { prompt, run, sessions, chat } -}) - -// Loop semantics - -it.live("loop exits immediately when last assistant has stop finish", () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ title: "Pinned" }) - yield* seed(chat.id, { finish: "stop" }) - - const result = yield* prompt.loop({ sessionID: chat.id }) - expect(result.info.role).toBe("assistant") - if (result.info.role === "assistant") expect(result.info.finish).toBe("stop") - expect(yield* llm.calls).toBe(0) - }), - { git: true, config: providerCfg }, - ), -) - -it.live("loop calls LLM and returns assistant message", () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ - title: "Pinned", - permission: [{ permission: "*", pattern: "*", action: "allow" }], - }) - yield* prompt.prompt({ - sessionID: chat.id, - agent: "build", - noReply: true, - parts: [{ type: "text", text: "hello" }], - }) - yield* llm.text("world") - - const result = yield* prompt.loop({ sessionID: chat.id }) - expect(result.info.role).toBe("assistant") - const parts = result.parts.filter((p) => p.type === "text") - expect(parts.some((p) => p.type === "text" && p.text === "world")).toBe(true) - expect(yield* llm.hits).toHaveLength(1) - }), - { git: true, config: providerCfg }, - ), -) - -it.live("static loop returns assistant text through local provider", () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({ - title: "Prompt provider", - permission: [{ permission: "*", pattern: "*", action: "allow" }], - }) - - yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - noReply: true, - parts: [{ type: "text", text: "hello" }], - }) - - yield* llm.text("world") - - const result = yield* prompt.loop({ sessionID: session.id }) - expect(result.info.role).toBe("assistant") - expect(result.parts.some((part) => part.type === "text" && part.text === "world")).toBe(true) - expect(yield* llm.hits).toHaveLength(1) - expect(yield* llm.pending).toBe(0) - }), - { git: true, config: providerCfg }, - ), -) - -it.live("static loop consumes queued replies across turns", () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({ - title: "Prompt provider turns", - permission: [{ permission: "*", pattern: "*", action: "allow" }], - }) - - yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - noReply: true, - parts: [{ type: "text", text: "hello one" }], - }) - - yield* llm.text("world one") - - const first = yield* prompt.loop({ sessionID: session.id }) - expect(first.info.role).toBe("assistant") - expect(first.parts.some((part) => part.type === "text" && part.text === "world one")).toBe(true) - - yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - noReply: true, - parts: [{ type: "text", text: "hello two" }], - }) - - yield* llm.text("world two") - - const second = yield* prompt.loop({ sessionID: session.id }) - expect(second.info.role).toBe("assistant") - expect(second.parts.some((part) => part.type === "text" && part.text === "world two")).toBe(true) - - expect(yield* llm.hits).toHaveLength(2) - expect(yield* llm.pending).toBe(0) - }), - { git: true, config: providerCfg }, - ), -) - -it.live("loop continues when finish is tool-calls", () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({ - title: "Pinned", - permission: [{ permission: "*", pattern: "*", action: "allow" }], - }) - yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - noReply: true, - parts: [{ type: "text", text: "hello" }], - }) - yield* llm.tool("first", { value: "first" }) - yield* llm.text("second") - - const result = yield* prompt.loop({ sessionID: session.id }) - expect(yield* llm.calls).toBe(2) - expect(result.info.role).toBe("assistant") - if (result.info.role === "assistant") { - expect(result.parts.some((part) => part.type === "text" && part.text === "second")).toBe(true) - expect(result.info.finish).toBe("stop") - } - }), - { git: true, config: providerCfg }, - ), -) - -it.live("glob tool keeps instance context during prompt runs", () => - provideTmpdirServer( - ({ dir, llm }) => - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({ - title: "Glob context", - permission: [{ permission: "*", pattern: "*", action: "allow" }], - }) - const file = path.join(dir, "probe.txt") - yield* Effect.promise(() => Bun.write(file, "probe")) - - yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - noReply: true, - parts: [{ type: "text", text: "find text files" }], - }) - yield* llm.tool("glob", { pattern: "**/*.txt" }) - yield* llm.text("done") - - const result = yield* prompt.loop({ sessionID: session.id }) - expect(result.info.role).toBe("assistant") - - const msgs = yield* MessageV2.filterCompactedEffect(session.id) - const tool = msgs - .flatMap((msg) => msg.parts) - .find( - (part): part is CompletedToolPart => - part.type === "tool" && part.tool === "glob" && part.state.status === "completed", - ) - if (!tool) return - - expect(tool.state.output).toContain(file) - expect(tool.state.output).not.toContain("No context found for instance") - expect(result.parts.some((part) => part.type === "text" && part.text === "done")).toBe(true) - }), - { git: true, config: providerCfg }, - ), -) - -it.live("loop continues when finish is stop but assistant has tool parts", () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({ - title: "Pinned", - permission: [{ permission: "*", pattern: "*", action: "allow" }], - }) - yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - noReply: true, - parts: [{ type: "text", text: "hello" }], - }) - yield* llm.push(reply().tool("first", { value: "first" }).stop()) - yield* llm.text("second") - - const result = yield* prompt.loop({ sessionID: session.id }) - expect(yield* llm.calls).toBe(2) - expect(result.info.role).toBe("assistant") - if (result.info.role === "assistant") { - expect(result.parts.some((part) => part.type === "text" && part.text === "second")).toBe(true) - expect(result.info.finish).toBe("stop") - } - }), - { git: true, config: providerCfg }, - ), -) - -it.live("failed subtask preserves metadata on error tool state", () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ title: "Pinned" }) - yield* llm.tool("task", { - description: "inspect bug", - prompt: "look into the cache key path", - subagent_type: "general", - }) - yield* llm.text("done") - const msg = yield* user(chat.id, "hello") - yield* addSubtask(chat.id, msg.id) - - const result = yield* prompt.loop({ sessionID: chat.id }) - expect(result.info.role).toBe("assistant") - expect(yield* llm.calls).toBe(2) - - const msgs = yield* MessageV2.filterCompactedEffect(chat.id) - const taskMsg = msgs.find((item) => item.info.role === "assistant" && item.info.agent === "general") - expect(taskMsg?.info.role).toBe("assistant") - if (!taskMsg || taskMsg.info.role !== "assistant") return - - const tool = errorTool(taskMsg.parts) - if (!tool) return - - expect(tool.state.error).toContain("Tool execution failed") - expect(tool.state.metadata).toBeDefined() - expect(tool.state.metadata?.sessionId).toBeDefined() - expect(tool.state.metadata?.model).toEqual({ - providerID: ProviderID.make("test"), - modelID: ModelID.make("missing-model"), - }) - }), - { - git: true, - config: (url) => ({ - ...providerCfg(url), - agent: { - general: { - model: "test/missing-model", - }, - }, - }), - }, - ), -) - -it.live( - "running subtask preserves metadata after tool-call transition", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ title: "Pinned" }) - yield* llm.hang - const msg = yield* user(chat.id, "hello") - yield* addSubtask(chat.id, msg.id) - - const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - - const tool = yield* Effect.promise(async () => { - const end = Date.now() + 5_000 - while (Date.now() < end) { - const msgs = await Effect.runPromise(MessageV2.filterCompactedEffect(chat.id)) - const taskMsg = msgs.find((item) => item.info.role === "assistant" && item.info.agent === "general") - const tool = taskMsg?.parts.find((part): part is MessageV2.ToolPart => part.type === "tool") - if (tool?.state.status === "running" && tool.state.metadata?.sessionId) return tool - await new Promise((done) => setTimeout(done, 20)) - } - throw new Error("timed out waiting for running subtask metadata") - }) - - if (tool.state.status !== "running") return - expect(typeof tool.state.metadata?.sessionId).toBe("string") - expect(tool.state.title).toBeDefined() - expect(tool.state.metadata?.model).toBeDefined() - - yield* prompt.cancel(chat.id) - yield* Fiber.await(fiber) - }), - { git: true, config: providerCfg }, - ), - 5_000, -) - -it.live( - "running task tool preserves metadata after tool-call transition", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ - title: "Pinned", - permission: [{ permission: "*", pattern: "*", action: "allow" }], - }) - yield* llm.tool("task", { - description: "inspect bug", - prompt: "look into the cache key path", - subagent_type: "general", - }) - yield* llm.hang - yield* user(chat.id, "hello") - - const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - - const tool = yield* Effect.promise(async () => { - const end = Date.now() + 5_000 - while (Date.now() < end) { - const msgs = await Effect.runPromise(MessageV2.filterCompactedEffect(chat.id)) - const assistant = msgs.findLast((item) => item.info.role === "assistant" && item.info.agent === "build") - const tool = assistant?.parts.find( - (part): part is MessageV2.ToolPart => part.type === "tool" && part.tool === "task", - ) - if (tool?.state.status === "running" && tool.state.metadata?.sessionId) return tool - await new Promise((done) => setTimeout(done, 20)) - } - throw new Error("timed out waiting for running task metadata") - }) - - if (tool.state.status !== "running") return - expect(typeof tool.state.metadata?.sessionId).toBe("string") - expect(tool.state.title).toBe("inspect bug") - expect(tool.state.metadata?.model).toBeDefined() - - yield* prompt.cancel(chat.id) - yield* Fiber.await(fiber) - }), - { git: true, config: providerCfg }, - ), - 10_000, -) - -it.live( - "loop sets status to busy then idle", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const status = yield* SessionStatus.Service - - yield* llm.hang - - const chat = yield* sessions.create({}) - yield* user(chat.id, "hi") - - const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* llm.wait(1) - expect((yield* status.get(chat.id)).type).toBe("busy") - yield* prompt.cancel(chat.id) - yield* Fiber.await(fiber) - expect((yield* status.get(chat.id)).type).toBe("idle") - }), - { git: true, config: providerCfg }, - ), - 3_000, -) - -// Cancel semantics - -it.live( - "cancel interrupts loop and resolves with an assistant message", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ title: "Pinned" }) - yield* seed(chat.id) - - yield* llm.hang - - yield* user(chat.id, "more") - - const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* llm.wait(1) - yield* prompt.cancel(chat.id) - const exit = yield* Fiber.await(fiber) - expect(Exit.isSuccess(exit)).toBe(true) - if (Exit.isSuccess(exit)) { - expect(exit.value.info.role).toBe("assistant") - } - }), - { git: true, config: providerCfg }, - ), - 3_000, -) - -it.live( - "cancel records MessageAbortedError on interrupted process", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ title: "Pinned" }) - yield* llm.hang - yield* user(chat.id, "hello") - - const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* llm.wait(1) - yield* prompt.cancel(chat.id) - const exit = yield* Fiber.await(fiber) - expect(Exit.isSuccess(exit)).toBe(true) - if (Exit.isSuccess(exit)) { - const info = exit.value.info - if (info.role === "assistant") { - expect(info.error?.name).toBe("MessageAbortedError") - } - } - }), - { git: true, config: providerCfg }, - ), - 3_000, -) - -it.live( - "cancel finalizes subtask tool state", - () => - provideTmpdirInstance( - () => - Effect.gen(function* () { - const ready = defer() - const aborted = defer() - const registry = yield* ToolRegistry.Service - const { task } = yield* registry.named() - const original = task.execute - task.execute = (_args, ctx) => - Effect.callback((_resume) => { - ready.resolve() - ctx.abort.addEventListener("abort", () => aborted.resolve(), { once: true }) - return Effect.sync(() => aborted.resolve()) - }) - yield* Effect.addFinalizer(() => Effect.sync(() => void (task.execute = original))) - - const { prompt, chat } = yield* boot() - const msg = yield* user(chat.id, "hello") - yield* addSubtask(chat.id, msg.id) - - const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* Effect.promise(() => ready.promise) - yield* prompt.cancel(chat.id) - yield* Effect.promise(() => aborted.promise) - - const exit = yield* Fiber.await(fiber) - expect(Exit.isSuccess(exit)).toBe(true) - - const msgs = yield* MessageV2.filterCompactedEffect(chat.id) - const taskMsg = msgs.find((item) => item.info.role === "assistant" && item.info.agent === "general") - expect(taskMsg?.info.role).toBe("assistant") - if (!taskMsg || taskMsg.info.role !== "assistant") return - - const tool = toolPart(taskMsg.parts) - expect(tool?.type).toBe("tool") - if (!tool) return - - expect(tool.state.status).not.toBe("running") - expect(taskMsg.info.time.completed).toBeDefined() - expect(taskMsg.info.finish).toBeDefined() - }), - { git: true, config: cfg }, - ), - 30_000, -) - -it.live( - "cancel with queued callers resolves all cleanly", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ title: "Pinned" }) - yield* llm.hang - yield* user(chat.id, "hello") - - const a = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* llm.wait(1) - const b = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* Effect.sleep(50) - - yield* prompt.cancel(chat.id) - const [exitA, exitB] = yield* Effect.all([Fiber.await(a), Fiber.await(b)]) - expect(Exit.isSuccess(exitA)).toBe(true) - expect(Exit.isSuccess(exitB)).toBe(true) - if (Exit.isSuccess(exitA) && Exit.isSuccess(exitB)) { - expect(exitA.value.info.id).toBe(exitB.value.info.id) - } - }), - { git: true, config: providerCfg }, - ), - 3_000, -) - -// Queue semantics - -it.live("concurrent loop callers get same result", () => - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const { prompt, run, chat } = yield* boot() - yield* seed(chat.id, { finish: "stop" }) - - const [a, b] = yield* Effect.all([prompt.loop({ sessionID: chat.id }), prompt.loop({ sessionID: chat.id })], { - concurrency: "unbounded", - }) - - expect(a.info.id).toBe(b.info.id) - expect(a.info.role).toBe("assistant") - yield* run.assertNotBusy(chat.id) - }), - { git: true }, - ), -) - -it.live( - "concurrent loop callers all receive same error result", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ title: "Pinned" }) - - yield* llm.fail("boom") - yield* user(chat.id, "hello") - - const [a, b] = yield* Effect.all([prompt.loop({ sessionID: chat.id }), prompt.loop({ sessionID: chat.id })], { - concurrency: "unbounded", - }) - expect(a.info.id).toBe(b.info.id) - expect(a.info.role).toBe("assistant") - }), - { git: true, config: providerCfg }, - ), - 3_000, -) - -it.live( - "prompt submitted during an active run is included in the next LLM input", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const gate = defer() - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ title: "Pinned" }) - - yield* llm.hold("first", gate.promise) - yield* llm.text("second") - - const a = yield* prompt - .prompt({ - sessionID: chat.id, - agent: "build", - model: ref, - parts: [{ type: "text", text: "first" }], - }) - .pipe(Effect.forkChild) - - yield* llm.wait(1) - - const id = MessageID.ascending() - const b = yield* prompt - .prompt({ - sessionID: chat.id, - messageID: id, - agent: "build", - model: ref, - parts: [{ type: "text", text: "second" }], - }) - .pipe(Effect.forkChild) - - yield* Effect.promise(async () => { - const end = Date.now() + 5000 - while (Date.now() < end) { - const msgs = await Effect.runPromise(sessions.messages({ sessionID: chat.id })) - if (msgs.some((msg) => msg.info.role === "user" && msg.info.id === id)) return - await new Promise((done) => setTimeout(done, 20)) - } - throw new Error("timed out waiting for second prompt to save") - }) - - gate.resolve() - - const [ea, eb] = yield* Effect.all([Fiber.await(a), Fiber.await(b)]) - expect(Exit.isSuccess(ea)).toBe(true) - expect(Exit.isSuccess(eb)).toBe(true) - expect(yield* llm.calls).toBe(2) - - const msgs = yield* sessions.messages({ sessionID: chat.id }) - const assistants = msgs.filter((msg) => msg.info.role === "assistant") - expect(assistants).toHaveLength(2) - const last = assistants.at(-1) - if (!last || last.info.role !== "assistant") throw new Error("expected second assistant") - expect(last.info.parentID).toBe(id) - expect(last.parts.some((part) => part.type === "text" && part.text === "second")).toBe(true) - - const inputs = yield* llm.inputs - expect(inputs).toHaveLength(2) - expect(JSON.stringify(inputs.at(-1)?.messages)).toContain("second") - }), - { git: true, config: providerCfg }, - ), - 3_000, -) - -it.live( - "assertNotBusy throws BusyError when loop running", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const run = yield* SessionRunState.Service - const sessions = yield* Session.Service - yield* llm.hang - - const chat = yield* sessions.create({}) - yield* user(chat.id, "hi") - - const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* llm.wait(1) - - const exit = yield* run.assertNotBusy(chat.id).pipe(Effect.exit) - expect(Exit.isFailure(exit)).toBe(true) - if (Exit.isFailure(exit)) { - expect(Cause.squash(exit.cause)).toBeInstanceOf(Session.BusyError) - } - - yield* prompt.cancel(chat.id) - yield* Fiber.await(fiber) - }), - { git: true, config: providerCfg }, - ), - 3_000, -) - -it.live("assertNotBusy succeeds when idle", () => - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const run = yield* SessionRunState.Service - const sessions = yield* Session.Service - - const chat = yield* sessions.create({}) - const exit = yield* run.assertNotBusy(chat.id).pipe(Effect.exit) - expect(Exit.isSuccess(exit)).toBe(true) - }), - { git: true }, - ), -) - -// Shell semantics - -it.live( - "shell rejects with BusyError when loop running", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ title: "Pinned" }) - yield* llm.hang - yield* user(chat.id, "hi") - - const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* llm.wait(1) - - const exit = yield* prompt.shell({ sessionID: chat.id, agent: "build", command: "echo hi" }).pipe(Effect.exit) - expect(Exit.isFailure(exit)).toBe(true) - if (Exit.isFailure(exit)) { - expect(Cause.squash(exit.cause)).toBeInstanceOf(Session.BusyError) - } - - yield* prompt.cancel(chat.id) - yield* Fiber.await(fiber) - }), - { git: true, config: providerCfg }, - ), - 3_000, -) - -unix("shell captures stdout and stderr in completed tool output", () => - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const { prompt, run, chat } = yield* boot() - const result = yield* prompt.shell({ - sessionID: chat.id, - agent: "build", - command: "printf out && printf err >&2", - }) - - expect(result.info.role).toBe("assistant") - const tool = completedTool(result.parts) - if (!tool) return - - expect(tool.state.output).toContain("out") - expect(tool.state.output).toContain("err") - expect(tool.state.metadata.output).toContain("out") - expect(tool.state.metadata.output).toContain("err") - yield* run.assertNotBusy(chat.id) - }), - { git: true, config: cfg }, - ), -) - -unix("shell completes a fast command on the preferred shell", () => - provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const { prompt, run, chat } = yield* boot() - const result = yield* prompt.shell({ - sessionID: chat.id, - agent: "build", - command: "pwd", - }) - - expect(result.info.role).toBe("assistant") - const tool = completedTool(result.parts) - if (!tool) return - - expect(tool.state.input.command).toBe("pwd") - expect(tool.state.output).toContain(dir) - expect(tool.state.metadata.output).toContain(dir) - yield* run.assertNotBusy(chat.id) - }), - { git: true, config: cfg }, - ), -) - -unix("shell lists files from the project directory", () => - provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const { prompt, run, chat } = yield* boot() - yield* Effect.promise(() => Bun.write(path.join(dir, "README.md"), "# e2e\n")) - - const result = yield* prompt.shell({ - sessionID: chat.id, - agent: "build", - command: "command ls", - }) - - expect(result.info.role).toBe("assistant") - const tool = completedTool(result.parts) - if (!tool) return - - expect(tool.state.input.command).toBe("command ls") - expect(tool.state.output).toContain("README.md") - expect(tool.state.metadata.output).toContain("README.md") - yield* run.assertNotBusy(chat.id) - }), - { git: true, config: cfg }, - ), -) - -unix("shell captures stderr from a failing command", () => - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const { prompt, run, chat } = yield* boot() - const result = yield* prompt.shell({ - sessionID: chat.id, - agent: "build", - command: "command -v __nonexistent_cmd_e2e__ || echo 'not found' >&2; exit 1", - }) - - expect(result.info.role).toBe("assistant") - const tool = completedTool(result.parts) - if (!tool) return - - expect(tool.state.output).toContain("not found") - expect(tool.state.metadata.output).toContain("not found") - yield* run.assertNotBusy(chat.id) - }), - { git: true, config: cfg }, - ), -) - -unix( - "shell updates running metadata before process exit", - () => - withSh(() => - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const { prompt, chat } = yield* boot() - - const fiber = yield* prompt - .shell({ sessionID: chat.id, agent: "build", command: "printf first && sleep 0.2 && printf second" }) - .pipe(Effect.forkChild) - - yield* Effect.promise(async () => { - const start = Date.now() - while (Date.now() - start < 5000) { - const msgs = await MessageV2.filterCompacted(MessageV2.stream(chat.id)) - const taskMsg = msgs.find((item) => item.info.role === "assistant") - const tool = taskMsg ? toolPart(taskMsg.parts) : undefined - if (tool?.state.status === "running" && tool.state.metadata?.output.includes("first")) return - await new Promise((done) => setTimeout(done, 20)) - } - throw new Error("timed out waiting for running shell metadata") - }) - - const exit = yield* Fiber.await(fiber) - expect(Exit.isSuccess(exit)).toBe(true) - }), - { git: true, config: cfg }, - ), - ), - 30_000, -) - -it.live( - "loop waits while shell runs and starts after shell exits", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ - title: "Pinned", - permission: [{ permission: "*", pattern: "*", action: "allow" }], - }) - yield* llm.text("after-shell") - - const sh = yield* prompt - .shell({ sessionID: chat.id, agent: "build", command: "sleep 0.2" }) - .pipe(Effect.forkChild) - yield* Effect.sleep(50) - - const loop = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* Effect.sleep(50) - - expect(yield* llm.calls).toBe(0) - - yield* Fiber.await(sh) - const exit = yield* Fiber.await(loop) - - expect(Exit.isSuccess(exit)).toBe(true) - if (Exit.isSuccess(exit)) { - expect(exit.value.info.role).toBe("assistant") - expect(exit.value.parts.some((part) => part.type === "text" && part.text === "after-shell")).toBe(true) - } - expect(yield* llm.calls).toBe(1) - }), - { git: true, config: providerCfg }, - ), - 3_000, -) - -it.live( - "shell completion resumes queued loop callers", - () => - provideTmpdirServer( - Effect.fnUntraced(function* ({ llm }) { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ - title: "Pinned", - permission: [{ permission: "*", pattern: "*", action: "allow" }], - }) - yield* llm.text("done") - - const sh = yield* prompt - .shell({ sessionID: chat.id, agent: "build", command: "sleep 0.2" }) - .pipe(Effect.forkChild) - yield* Effect.sleep(50) - - const a = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - const b = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* Effect.sleep(50) - - expect(yield* llm.calls).toBe(0) - - yield* Fiber.await(sh) - const [ea, eb] = yield* Effect.all([Fiber.await(a), Fiber.await(b)]) - - expect(Exit.isSuccess(ea)).toBe(true) - expect(Exit.isSuccess(eb)).toBe(true) - if (Exit.isSuccess(ea) && Exit.isSuccess(eb)) { - expect(ea.value.info.id).toBe(eb.value.info.id) - expect(ea.value.info.role).toBe("assistant") - } - expect(yield* llm.calls).toBe(1) - }), - { git: true, config: providerCfg }, - ), - 3_000, -) - -unix( - "cancel interrupts shell and resolves cleanly", - () => - withSh(() => - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const { prompt, run, chat } = yield* boot() - - const sh = yield* prompt - .shell({ sessionID: chat.id, agent: "build", command: "sleep 30" }) - .pipe(Effect.forkChild) - yield* Effect.sleep(50) - - yield* prompt.cancel(chat.id) - - const status = yield* SessionStatus.Service - expect((yield* status.get(chat.id)).type).toBe("idle") - const busy = yield* run.assertNotBusy(chat.id).pipe(Effect.exit) - expect(Exit.isSuccess(busy)).toBe(true) - - const exit = yield* Fiber.await(sh) - expect(Exit.isSuccess(exit)).toBe(true) - if (Exit.isSuccess(exit)) { - expect(exit.value.info.role).toBe("assistant") - const tool = completedTool(exit.value.parts) - if (tool) { - expect(tool.state.output).toContain("User aborted the command") - } - } - }), - { git: true, config: cfg }, - ), - ), - 30_000, -) - -unix( - "cancel persists aborted shell result when shell ignores TERM", - () => - withSh(() => - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const { prompt, chat } = yield* boot() - - const sh = yield* prompt - .shell({ sessionID: chat.id, agent: "build", command: "trap '' TERM; sleep 30" }) - .pipe(Effect.forkChild) - yield* Effect.sleep(50) - - yield* prompt.cancel(chat.id) - - const exit = yield* Fiber.await(sh) - expect(Exit.isSuccess(exit)).toBe(true) - if (Exit.isSuccess(exit)) { - expect(exit.value.info.role).toBe("assistant") - const tool = completedTool(exit.value.parts) - if (tool) { - expect(tool.state.output).toContain("User aborted the command") - } - } - }), - { git: true, config: cfg }, - ), - ), - 30_000, -) - -unix( - "cancel finalizes interrupted bash tool output through normal truncation", - () => - provideTmpdirServer( - ({ dir, llm }) => - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ - title: "Interrupted bash truncation", - permission: [{ permission: "*", pattern: "*", action: "allow" }], - }) - - yield* prompt.prompt({ - sessionID: chat.id, - agent: "build", - noReply: true, - parts: [{ type: "text", text: "run bash" }], - }) - - yield* llm.tool("bash", { - command: - 'i=0; while [ "$i" -lt 4000 ]; do printf "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx %05d\\n" "$i"; i=$((i + 1)); done; sleep 30', - description: "Print many lines", - timeout: 30_000, - workdir: path.resolve(dir), - }) - - const run = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* llm.wait(1) - yield* Effect.sleep(150) - yield* prompt.cancel(chat.id) - - const exit = yield* Fiber.await(run) - expect(Exit.isSuccess(exit)).toBe(true) - if (Exit.isFailure(exit)) return - - const tool = completedTool(exit.value.parts) - if (!tool) return - - expect(tool.state.metadata.truncated).toBe(true) - expect(typeof tool.state.metadata.outputPath).toBe("string") - expect(tool.state.output).toMatch(/\.\.\.output truncated\.\.\./) - expect(tool.state.output).toMatch(/Full output saved to:\s+\S+/) - expect(tool.state.output).not.toContain("Tool execution aborted") - }), - { git: true, config: providerCfg }, - ), - 30_000, -) - -unix( - "cancel interrupts loop queued behind shell", - () => - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const { prompt, chat } = yield* boot() - - const sh = yield* prompt - .shell({ sessionID: chat.id, agent: "build", command: "sleep 30" }) - .pipe(Effect.forkChild) - yield* Effect.sleep(50) - - const loop = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* Effect.sleep(50) - - yield* prompt.cancel(chat.id) - - const exit = yield* Fiber.await(loop) - expect(Exit.isSuccess(exit)).toBe(true) - - yield* Fiber.await(sh) - }), - { git: true, config: cfg }, - ), - 30_000, -) - -unix( - "shell rejects when another shell is already running", - () => - withSh(() => - provideTmpdirInstance( - (_dir) => - Effect.gen(function* () { - const { prompt, chat } = yield* boot() - - const a = yield* prompt - .shell({ sessionID: chat.id, agent: "build", command: "sleep 30" }) - .pipe(Effect.forkChild) - yield* Effect.sleep(50) - - const exit = yield* prompt - .shell({ sessionID: chat.id, agent: "build", command: "echo hi" }) - .pipe(Effect.exit) - expect(Exit.isFailure(exit)).toBe(true) - if (Exit.isFailure(exit)) { - expect(Cause.squash(exit.cause)).toBeInstanceOf(Session.BusyError) - } - - yield* prompt.cancel(chat.id) - yield* Fiber.await(a) - }), - { git: true, config: cfg }, - ), - ), - 30_000, -) - -// Abort signal propagation tests for inline tool execution - -/** Override a tool's execute to hang until aborted. Returns ready/aborted defers and a finalizer. */ -function hangUntilAborted(tool: { execute: (...args: any[]) => any }) { - const ready = defer() - const aborted = defer() - const original = tool.execute - tool.execute = (_args: any, ctx: any) => { - ready.resolve() - ctx.abort.addEventListener("abort", () => aborted.resolve(), { once: true }) - return Effect.callback(() => {}) - } - const restore = Effect.addFinalizer(() => Effect.sync(() => void (tool.execute = original))) - return { ready, aborted, restore } -} - -it.live( - "interrupt propagates abort signal to read tool via file part (text/plain)", - () => - provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const registry = yield* ToolRegistry.Service - const { read } = yield* registry.named() - const { ready, aborted, restore } = hangUntilAborted(read) - yield* restore - - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ title: "Abort Test" }) - - const testFile = path.join(dir, "test.txt") - yield* Effect.promise(() => Bun.write(testFile, "hello world")) - - const fiber = yield* prompt - .prompt({ - sessionID: chat.id, - agent: "build", - parts: [ - { type: "text", text: "read this" }, - { type: "file", url: `file://${testFile}`, filename: "test.txt", mime: "text/plain" }, - ], - }) - .pipe(Effect.forkChild) - - yield* Effect.promise(() => ready.promise) - yield* Fiber.interrupt(fiber) - - yield* Effect.promise(() => - Promise.race([ - aborted.promise, - new Promise((_, reject) => - setTimeout(() => reject(new Error("abort signal not propagated within 2s")), 2_000), - ), - ]), - ) - }), - { git: true, config: cfg }, - ), - 30_000, -) - -it.live( - "interrupt propagates abort signal to read tool via file part (directory)", - () => - provideTmpdirInstance( - (dir) => - Effect.gen(function* () { - const registry = yield* ToolRegistry.Service - const { read } = yield* registry.named() - const { ready, aborted, restore } = hangUntilAborted(read) - yield* restore - - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const chat = yield* sessions.create({ title: "Abort Test" }) - - const fiber = yield* prompt - .prompt({ - sessionID: chat.id, - agent: "build", - parts: [ - { type: "text", text: "read this" }, - { type: "file", url: `file://${dir}`, filename: "dir", mime: "application/x-directory" }, - ], - }) - .pipe(Effect.forkChild) - - yield* Effect.promise(() => ready.promise) - yield* Fiber.interrupt(fiber) - - yield* Effect.promise(() => - Promise.race([ - aborted.promise, - new Promise((_, reject) => - setTimeout(() => reject(new Error("abort signal not propagated within 2s")), 2_000), - ), - ]), - ) - }), - { git: true, config: cfg }, - ), - 30_000, -) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 2b489da9e..8ffb20f15 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1,22 +1,64 @@ +import { NodeFileSystem } from "@effect/platform-node" +import { FetchHttpClient } from "effect/unstable/http" +import { expect } from "bun:test" +import { Cause, Effect, Exit, Fiber, Layer } from "effect" import path from "path" -import { describe, expect, test } from "bun:test" -import { NamedError } from "@opencode-ai/shared/util/error" import { fileURLToPath } from "url" -import { Effect, Layer } from "effect" -import { Instance } from "../../src/project/instance" +import { NamedError } from "@opencode-ai/shared/util/error" +import { Agent as AgentSvc } from "../../src/agent/agent" +import { Bus } from "../../src/bus" +import { Command } from "../../src/command" +import { Config } from "../../src/config" +import { LSP } from "../../src/lsp" +import { MCP } from "../../src/mcp" +import { Permission } from "../../src/permission" +import { Plugin } from "../../src/plugin" +import { Provider as ProviderSvc } from "../../src/provider" +import { Env } from "../../src/env" import { ModelID, ProviderID } from "../../src/provider/schema" +import { Question } from "../../src/question" +import { Todo } from "../../src/session/todo" import { Session } from "../../src/session" +import { LLM } from "../../src/session/llm" import { MessageV2 } from "../../src/session/message-v2" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { SessionCompaction } from "../../src/session/compaction" +import { SessionSummary } from "../../src/session/summary" +import { Instruction } from "../../src/session/instruction" +import { SessionProcessor } from "../../src/session/processor" import { SessionPrompt } from "../../src/session/prompt" +import { SessionRevert } from "../../src/session/revert" +import { SessionRunState } from "../../src/session/run-state" +import { MessageID, PartID, SessionID } from "../../src/session/schema" +import { SessionStatus } from "../../src/session/status" +import { Skill } from "../../src/skill" +import { SystemPrompt } from "../../src/session/system" +import { Shell } from "../../src/shell/shell" +import { Snapshot } from "../../src/snapshot" +import { ToolRegistry } from "../../src/tool" +import { Truncate } from "../../src/tool" import { Log } from "../../src/util" -import { tmpdir } from "../fixture/fixture" +import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { Ripgrep } from "../../src/file/ripgrep" +import { Format } from "../../src/format" +import { provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture" +import { testEffect } from "../lib/effect" +import { reply, TestLLMServer } from "../lib/llm-server" void Log.init({ print: false }) -function run(fx: Effect.Effect) { - return Effect.runPromise( - fx.pipe(Effect.scoped, Effect.provide(Layer.mergeAll(SessionPrompt.defaultLayer, Session.defaultLayer))), - ) +const summary = Layer.succeed( + SessionSummary.Service, + SessionSummary.Service.of({ + summarize: () => Effect.void, + diff: () => Effect.succeed([]), + computeDiff: () => Effect.succeed([]), + }), +) + +const ref = { + providerID: ProviderID.make("test"), + modelID: ModelID.make("test-model"), } function defer() { @@ -27,563 +69,1817 @@ function defer() { return { promise, resolve } } -function chat(text: string) { - const payload = - [ - `data: ${JSON.stringify({ - id: "chatcmpl-1", - object: "chat.completion.chunk", - choices: [{ delta: { role: "assistant" } }], - })}`, - `data: ${JSON.stringify({ - id: "chatcmpl-1", - object: "chat.completion.chunk", - choices: [{ delta: { content: text } }], - })}`, - `data: ${JSON.stringify({ - id: "chatcmpl-1", - object: "chat.completion.chunk", - choices: [{ delta: {}, finish_reason: "stop" }], - })}`, - "data: [DONE]", - ].join("\n\n") + "\n\n" - - const encoder = new TextEncoder() - return new ReadableStream({ - start(ctrl) { - ctrl.enqueue(encoder.encode(payload)) - ctrl.close() - }, - }) +function withSh(fx: () => Effect.Effect) { + return Effect.acquireUseRelease( + Effect.sync(() => { + const prev = process.env.SHELL + process.env.SHELL = "/bin/sh" + Shell.preferred.reset() + return prev + }), + () => fx(), + (prev) => + Effect.sync(() => { + if (prev === undefined) delete process.env.SHELL + else process.env.SHELL = prev + Shell.preferred.reset() + }), + ) } -function hanging(ready: () => void) { - const encoder = new TextEncoder() - let timer: ReturnType | undefined - const first = `data: ${JSON.stringify({ - id: "chatcmpl-1", - object: "chat.completion.chunk", - choices: [{ delta: { role: "assistant" } }], - })}\n\n` - const rest = - [ - `data: ${JSON.stringify({ - id: "chatcmpl-1", - object: "chat.completion.chunk", - choices: [{ delta: { content: "late" } }], - })}`, - `data: ${JSON.stringify({ - id: "chatcmpl-1", - object: "chat.completion.chunk", - choices: [{ delta: {}, finish_reason: "stop" }], - })}`, - "data: [DONE]", - ].join("\n\n") + "\n\n" - - return new ReadableStream({ - start(ctrl) { - ctrl.enqueue(encoder.encode(first)) - ready() - timer = setTimeout(() => { - ctrl.enqueue(encoder.encode(rest)) - ctrl.close() - }, 10000) - }, - cancel() { - if (timer) clearTimeout(timer) - }, - }) +function toolPart(parts: MessageV2.Part[]) { + return parts.find((part): part is MessageV2.ToolPart => part.type === "tool") } -describe("session.prompt missing file", () => { - test("does not fail the prompt when a file part is missing", async () => { - await using tmp = await tmpdir({ - git: true, - config: { - agent: { - build: { - model: "openai/gpt-5.2", - }, +type CompletedToolPart = MessageV2.ToolPart & { state: MessageV2.ToolStateCompleted } +type ErrorToolPart = MessageV2.ToolPart & { state: MessageV2.ToolStateError } + +function completedTool(parts: MessageV2.Part[]) { + const part = toolPart(parts) + expect(part?.state.status).toBe("completed") + return part?.state.status === "completed" ? (part as CompletedToolPart) : undefined +} + +function errorTool(parts: MessageV2.Part[]) { + const part = toolPart(parts) + expect(part?.state.status).toBe("error") + return part?.state.status === "error" ? (part as ErrorToolPart) : undefined +} + +const mcp = Layer.succeed( + MCP.Service, + MCP.Service.of({ + status: () => Effect.succeed({}), + clients: () => Effect.succeed({}), + tools: () => Effect.succeed({}), + prompts: () => Effect.succeed({}), + resources: () => Effect.succeed({}), + add: () => Effect.succeed({ status: { status: "disabled" as const } }), + connect: () => Effect.void, + disconnect: () => Effect.void, + getPrompt: () => Effect.succeed(undefined), + readResource: () => Effect.succeed(undefined), + startAuth: () => Effect.die("unexpected MCP auth in prompt-effect tests"), + authenticate: () => Effect.die("unexpected MCP auth in prompt-effect tests"), + finishAuth: () => Effect.die("unexpected MCP auth in prompt-effect tests"), + removeAuth: () => Effect.void, + supportsOAuth: () => Effect.succeed(false), + hasStoredTokens: () => Effect.succeed(false), + getAuthStatus: () => Effect.succeed("not_authenticated" as const), + }), +) + +const lsp = Layer.succeed( + LSP.Service, + LSP.Service.of({ + init: () => Effect.void, + status: () => Effect.succeed([]), + hasClients: () => Effect.succeed(false), + touchFile: () => Effect.void, + diagnostics: () => Effect.succeed({}), + hover: () => Effect.succeed(undefined), + definition: () => Effect.succeed([]), + references: () => Effect.succeed([]), + implementation: () => Effect.succeed([]), + documentSymbol: () => Effect.succeed([]), + workspaceSymbol: () => Effect.succeed([]), + prepareCallHierarchy: () => Effect.succeed([]), + incomingCalls: () => Effect.succeed([]), + outgoingCalls: () => Effect.succeed([]), + }), +) + +const status = SessionStatus.layer.pipe(Layer.provideMerge(Bus.layer)) +const run = SessionRunState.layer.pipe(Layer.provide(status)) +const infra = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer) +function makeHttp() { + const deps = Layer.mergeAll( + Session.defaultLayer, + Snapshot.defaultLayer, + LLM.defaultLayer, + Env.defaultLayer, + AgentSvc.defaultLayer, + Command.defaultLayer, + Permission.defaultLayer, + Plugin.defaultLayer, + Config.defaultLayer, + ProviderSvc.defaultLayer, + lsp, + mcp, + AppFileSystem.defaultLayer, + status, + ).pipe(Layer.provideMerge(infra)) + const question = Question.layer.pipe(Layer.provideMerge(deps)) + const todo = Todo.layer.pipe(Layer.provideMerge(deps)) + const registry = ToolRegistry.layer.pipe( + Layer.provide(Skill.defaultLayer), + Layer.provide(FetchHttpClient.layer), + Layer.provide(CrossSpawnSpawner.defaultLayer), + Layer.provide(Ripgrep.defaultLayer), + Layer.provide(Format.defaultLayer), + Layer.provideMerge(todo), + Layer.provideMerge(question), + Layer.provideMerge(deps), + ) + const trunc = Truncate.layer.pipe(Layer.provideMerge(deps)) + const proc = SessionProcessor.layer.pipe(Layer.provide(summary), Layer.provideMerge(deps)) + const compact = SessionCompaction.layer.pipe(Layer.provideMerge(proc), Layer.provideMerge(deps)) + return Layer.mergeAll( + TestLLMServer.layer, + SessionPrompt.layer.pipe( + Layer.provide(SessionRevert.defaultLayer), + Layer.provide(summary), + Layer.provideMerge(run), + Layer.provideMerge(compact), + Layer.provideMerge(proc), + Layer.provideMerge(registry), + Layer.provideMerge(trunc), + Layer.provide(Instruction.defaultLayer), + Layer.provide(SystemPrompt.defaultLayer), + Layer.provideMerge(deps), + ), + ).pipe(Layer.provide(summary)) +} + +const it = testEffect(makeHttp()) +const unix = process.platform !== "win32" ? it.live : it.live.skip + +// Config that registers a custom "test" provider with a "test-model" model +// so provider model lookup succeeds inside the loop. +const cfg = { + provider: { + test: { + name: "Test", + id: "test", + env: [], + npm: "@ai-sdk/openai-compatible", + models: { + "test-model": { + id: "test-model", + name: "Test Model", + attachment: false, + reasoning: false, + temperature: false, + tool_call: true, + release_date: "2025-01-01", + limit: { context: 100000, output: 10000 }, + cost: { input: 0, output: 0 }, + options: {}, }, }, - }) + options: { + apiKey: "test-key", + baseURL: "http://localhost:1/v1", + }, + }, + }, +} - await Instance.provide({ - directory: tmp.path, - fn: () => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({}) - - const missing = path.join(tmp.path, "does-not-exist.ts") - const msg = yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - noReply: true, - parts: [ - { type: "text", text: "please review @does-not-exist.ts" }, - { - type: "file", - mime: "text/plain", - url: `file://${missing}`, - filename: "does-not-exist.ts", - }, - ], - }) - - if (msg.info.role !== "user") throw new Error("expected user message") - - const hasFailure = msg.parts.some( - (part) => part.type === "text" && part.synthetic && part.text.includes("Read tool failed to read"), - ) - expect(hasFailure).toBe(true) - - yield* sessions.remove(session.id) - }), - ), - }) - }) - - test("keeps stored part order stable when file resolution is async", async () => { - await using tmp = await tmpdir({ - git: true, - config: { - agent: { - build: { - model: "openai/gpt-5.2", - }, +function providerCfg(url: string) { + return { + ...cfg, + provider: { + ...cfg.provider, + test: { + ...cfg.provider.test, + options: { + ...cfg.provider.test.options, + baseURL: url, }, }, - }) + }, + } +} - await Instance.provide({ - directory: tmp.path, - fn: () => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({}) - - const missing = path.join(tmp.path, "still-missing.ts") - const msg = yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - noReply: true, - parts: [ - { - type: "file", - mime: "text/plain", - url: `file://${missing}`, - filename: "still-missing.ts", - }, - { type: "text", text: "after-file" }, - ], - }) - - if (msg.info.role !== "user") throw new Error("expected user message") - - const stored = MessageV2.get({ - sessionID: session.id, - messageID: msg.info.id, - }) - const text = stored.parts.filter((part) => part.type === "text").map((part) => part.text) - - expect(text[0]?.startsWith("Called the Read tool with the following input:")).toBe(true) - expect(text[1]?.includes("Read tool failed to read")).toBe(true) - expect(text[2]).toBe("after-file") - - yield* sessions.remove(session.id) - }), - ), - }) +const user = Effect.fn("test.user")(function* (sessionID: SessionID, text: string) { + const session = yield* Session.Service + const msg = yield* session.updateMessage({ + id: MessageID.ascending(), + role: "user", + sessionID, + agent: "build", + model: ref, + time: { created: Date.now() }, }) + yield* session.updatePart({ + id: PartID.ascending(), + messageID: msg.id, + sessionID, + type: "text", + text, + }) + return msg }) -describe("session.prompt special characters", () => { - test("handles filenames with # character", async () => { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - await Bun.write(path.join(dir, "file#name.txt"), "special content\n") - }, - }) - - await Instance.provide({ - directory: tmp.path, - fn: () => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({}) - const template = "Read @file#name.txt" - const parts = yield* prompt.resolvePromptParts(template) - const fileParts = parts.filter((part) => part.type === "file") - - expect(fileParts.length).toBe(1) - expect(fileParts[0].filename).toBe("file#name.txt") - expect(fileParts[0].url).toContain("%23") - - const decodedPath = fileURLToPath(fileParts[0].url) - expect(decodedPath).toBe(path.join(tmp.path, "file#name.txt")) - - const message = yield* prompt.prompt({ - sessionID: session.id, - parts, - noReply: true, - }) - const stored = MessageV2.get({ sessionID: session.id, messageID: message.info.id }) - const textParts = stored.parts.filter((part) => part.type === "text") - const hasContent = textParts.some((part) => part.text.includes("special content")) - expect(hasContent).toBe(true) - - yield* sessions.remove(session.id) - }), - ), - }) +const seed = Effect.fn("test.seed")(function* (sessionID: SessionID, opts?: { finish?: string }) { + const session = yield* Session.Service + const msg = yield* user(sessionID, "hello") + const assistant: MessageV2.Assistant = { + id: MessageID.ascending(), + role: "assistant", + parentID: msg.id, + sessionID, + mode: "build", + agent: "build", + cost: 0, + path: { cwd: "/tmp", root: "/tmp" }, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + modelID: ref.modelID, + providerID: ref.providerID, + time: { created: Date.now() }, + ...(opts?.finish ? { finish: opts.finish } : {}), + } + yield* session.updateMessage(assistant) + yield* session.updatePart({ + id: PartID.ascending(), + messageID: assistant.id, + sessionID, + type: "text", + text: "hi there", }) + return { user: msg, assistant } }) -describe("session.prompt regression", () => { - test("does not loop empty assistant turns for a simple reply", async () => { - let calls = 0 - const server = Bun.serve({ - port: 0, - fetch(req) { - const url = new URL(req.url) - if (!url.pathname.endsWith("/chat/completions")) { - return new Response("not found", { status: 404 }) - } - calls++ - return new Response(chat("packages/opencode/src/session/processor.ts"), { - status: 200, - headers: { "Content-Type": "text/event-stream" }, +const addSubtask = (sessionID: SessionID, messageID: MessageID, model = ref) => + Effect.gen(function* () { + const session = yield* Session.Service + yield* session.updatePart({ + id: PartID.ascending(), + messageID, + sessionID, + type: "subtask", + prompt: "look into the cache key path", + description: "inspect bug", + agent: "general", + model, + }) + }) + +const boot = Effect.fn("test.boot")(function* (input?: { title?: string }) { + const prompt = yield* SessionPrompt.Service + const run = yield* SessionRunState.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create(input ?? { title: "Pinned" }) + return { prompt, run, sessions, chat } +}) + +// Loop semantics + +it.live("loop exits immediately when last assistant has stop finish", () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Pinned" }) + yield* seed(chat.id, { finish: "stop" }) + + const result = yield* prompt.loop({ sessionID: chat.id }) + expect(result.info.role).toBe("assistant") + if (result.info.role === "assistant") expect(result.info.finish).toBe("stop") + expect(yield* llm.calls).toBe(0) + }), + { git: true, config: providerCfg }, + ), +) + +it.live("loop calls LLM and returns assistant message", () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ + title: "Pinned", + permission: [{ permission: "*", pattern: "*", action: "allow" }], + }) + yield* prompt.prompt({ + sessionID: chat.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "hello" }], + }) + yield* llm.text("world") + + const result = yield* prompt.loop({ sessionID: chat.id }) + expect(result.info.role).toBe("assistant") + const parts = result.parts.filter((p) => p.type === "text") + expect(parts.some((p) => p.type === "text" && p.text === "world")).toBe(true) + expect(yield* llm.hits).toHaveLength(1) + }), + { git: true, config: providerCfg }, + ), +) + +it.live("static loop returns assistant text through local provider", () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({ + title: "Prompt provider", + permission: [{ permission: "*", pattern: "*", action: "allow" }], + }) + + yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "hello" }], + }) + + yield* llm.text("world") + + const result = yield* prompt.loop({ sessionID: session.id }) + expect(result.info.role).toBe("assistant") + expect(result.parts.some((part) => part.type === "text" && part.text === "world")).toBe(true) + expect(yield* llm.hits).toHaveLength(1) + expect(yield* llm.pending).toBe(0) + }), + { git: true, config: providerCfg }, + ), +) + +it.live("static loop consumes queued replies across turns", () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({ + title: "Prompt provider turns", + permission: [{ permission: "*", pattern: "*", action: "allow" }], + }) + + yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "hello one" }], + }) + + yield* llm.text("world one") + + const first = yield* prompt.loop({ sessionID: session.id }) + expect(first.info.role).toBe("assistant") + expect(first.parts.some((part) => part.type === "text" && part.text === "world one")).toBe(true) + + yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "hello two" }], + }) + + yield* llm.text("world two") + + const second = yield* prompt.loop({ sessionID: session.id }) + expect(second.info.role).toBe("assistant") + expect(second.parts.some((part) => part.type === "text" && part.text === "world two")).toBe(true) + + expect(yield* llm.hits).toHaveLength(2) + expect(yield* llm.pending).toBe(0) + }), + { git: true, config: providerCfg }, + ), +) + +it.live("loop continues when finish is tool-calls", () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({ + title: "Pinned", + permission: [{ permission: "*", pattern: "*", action: "allow" }], + }) + yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "hello" }], + }) + yield* llm.tool("first", { value: "first" }) + yield* llm.text("second") + + const result = yield* prompt.loop({ sessionID: session.id }) + expect(yield* llm.calls).toBe(2) + expect(result.info.role).toBe("assistant") + if (result.info.role === "assistant") { + expect(result.parts.some((part) => part.type === "text" && part.text === "second")).toBe(true) + expect(result.info.finish).toBe("stop") + } + }), + { git: true, config: providerCfg }, + ), +) + +it.live("glob tool keeps instance context during prompt runs", () => + provideTmpdirServer( + ({ dir, llm }) => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({ + title: "Glob context", + permission: [{ permission: "*", pattern: "*", action: "allow" }], }) - }, - }) + const file = path.join(dir, "probe.txt") + yield* Effect.promise(() => Bun.write(file, "probe")) - try { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - await Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - enabled_providers: ["alibaba"], - provider: { - alibaba: { - options: { - apiKey: "test-key", - baseURL: `${server.url.origin}/v1`, - }, - }, - }, - agent: { - build: { - model: "alibaba/qwen-plus", - }, - }, - }), + yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "find text files" }], + }) + yield* llm.tool("glob", { pattern: "**/*.txt" }) + yield* llm.text("done") + + const result = yield* prompt.loop({ sessionID: session.id }) + expect(result.info.role).toBe("assistant") + + const msgs = yield* MessageV2.filterCompactedEffect(session.id) + const tool = msgs + .flatMap((msg) => msg.parts) + .find( + (part): part is CompletedToolPart => + part.type === "tool" && part.tool === "glob" && part.state.status === "completed", ) - }, + if (!tool) return + + expect(tool.state.output).toContain(file) + expect(tool.state.output).not.toContain("No context found for instance") + expect(result.parts.some((part) => part.type === "text" && part.text === "done")).toBe(true) + }), + { git: true, config: providerCfg }, + ), +) + +it.live("loop continues when finish is stop but assistant has tool parts", () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({ + title: "Pinned", + permission: [{ permission: "*", pattern: "*", action: "allow" }], }) - - await Instance.provide({ - directory: tmp.path, - fn: () => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({ title: "Prompt regression" }) - const result = yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - parts: [{ type: "text", text: "Where is SessionProcessor?" }], - }) - - expect(result.info.role).toBe("assistant") - expect(result.parts.some((part) => part.type === "text" && part.text.includes("processor.ts"))).toBe(true) - - const msgs = yield* sessions.messages({ sessionID: session.id }) - expect(msgs.filter((msg) => msg.info.role === "assistant")).toHaveLength(1) - expect(calls).toBe(1) - }), - ), + yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "hello" }], }) - } finally { - void server.stop(true) - } - }) + yield* llm.push(reply().tool("first", { value: "first" }).stop()) + yield* llm.text("second") - test("records aborted errors when prompt is cancelled mid-stream", async () => { - const ready = defer() - const server = Bun.serve({ - port: 0, - fetch(req) { - const url = new URL(req.url) - if (!url.pathname.endsWith("/chat/completions")) { - return new Response("not found", { status: 404 }) - } - return new Response( - hanging(() => ready.resolve()), - { - status: 200, - headers: { "Content-Type": "text/event-stream" }, + const result = yield* prompt.loop({ sessionID: session.id }) + expect(yield* llm.calls).toBe(2) + expect(result.info.role).toBe("assistant") + if (result.info.role === "assistant") { + expect(result.parts.some((part) => part.type === "text" && part.text === "second")).toBe(true) + expect(result.info.finish).toBe("stop") + } + }), + { git: true, config: providerCfg }, + ), +) + +it.live("failed subtask preserves metadata on error tool state", () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Pinned" }) + yield* llm.tool("task", { + description: "inspect bug", + prompt: "look into the cache key path", + subagent_type: "general", + }) + yield* llm.text("done") + const msg = yield* user(chat.id, "hello") + yield* addSubtask(chat.id, msg.id) + + const result = yield* prompt.loop({ sessionID: chat.id }) + expect(result.info.role).toBe("assistant") + expect(yield* llm.calls).toBe(2) + + const msgs = yield* MessageV2.filterCompactedEffect(chat.id) + const taskMsg = msgs.find((item) => item.info.role === "assistant" && item.info.agent === "general") + expect(taskMsg?.info.role).toBe("assistant") + if (!taskMsg || taskMsg.info.role !== "assistant") return + + const tool = errorTool(taskMsg.parts) + if (!tool) return + + expect(tool.state.error).toContain("Tool execution failed") + expect(tool.state.metadata).toBeDefined() + expect(tool.state.metadata?.sessionId).toBeDefined() + expect(tool.state.metadata?.model).toEqual({ + providerID: ProviderID.make("test"), + modelID: ModelID.make("missing-model"), + }) + }), + { + git: true, + config: (url) => ({ + ...providerCfg(url), + agent: { + general: { + model: "test/missing-model", }, - ) - }, - }) - - try { - await using tmp = await tmpdir({ - git: true, - init: async (dir) => { - await Bun.write( - path.join(dir, "opencode.json"), - JSON.stringify({ - $schema: "https://opencode.ai/config.json", - enabled_providers: ["alibaba"], - provider: { - alibaba: { - options: { - apiKey: "test-key", - baseURL: `${server.url.origin}/v1`, - }, - }, - }, - agent: { - build: { - model: "alibaba/qwen-plus", - }, - }, - }), - ) }, + }), + }, + ), +) + +it.live( + "running subtask preserves metadata after tool-call transition", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Pinned" }) + yield* llm.hang + const msg = yield* user(chat.id, "hello") + yield* addSubtask(chat.id, msg.id) + + const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + + const tool = yield* Effect.promise(async () => { + const end = Date.now() + 5_000 + while (Date.now() < end) { + const msgs = await Effect.runPromise(MessageV2.filterCompactedEffect(chat.id)) + const taskMsg = msgs.find((item) => item.info.role === "assistant" && item.info.agent === "general") + const tool = taskMsg?.parts.find((part): part is MessageV2.ToolPart => part.type === "tool") + if (tool?.state.status === "running" && tool.state.metadata?.sessionId) return tool + await new Promise((done) => setTimeout(done, 20)) + } + throw new Error("timed out waiting for running subtask metadata") + }) + + if (tool.state.status !== "running") return + expect(typeof tool.state.metadata?.sessionId).toBe("string") + expect(tool.state.title).toBeDefined() + expect(tool.state.metadata?.model).toBeDefined() + + yield* prompt.cancel(chat.id) + yield* Fiber.await(fiber) + }), + { git: true, config: providerCfg }, + ), + 5_000, +) + +it.live( + "running task tool preserves metadata after tool-call transition", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ + title: "Pinned", + permission: [{ permission: "*", pattern: "*", action: "allow" }], + }) + yield* llm.tool("task", { + description: "inspect bug", + prompt: "look into the cache key path", + subagent_type: "general", + }) + yield* llm.hang + yield* user(chat.id, "hello") + + const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + + const tool = yield* Effect.promise(async () => { + const end = Date.now() + 5_000 + while (Date.now() < end) { + const msgs = await Effect.runPromise(MessageV2.filterCompactedEffect(chat.id)) + const assistant = msgs.findLast((item) => item.info.role === "assistant" && item.info.agent === "build") + const tool = assistant?.parts.find( + (part): part is MessageV2.ToolPart => part.type === "tool" && part.tool === "task", + ) + if (tool?.state.status === "running" && tool.state.metadata?.sessionId) return tool + await new Promise((done) => setTimeout(done, 20)) + } + throw new Error("timed out waiting for running task metadata") + }) + + if (tool.state.status !== "running") return + expect(typeof tool.state.metadata?.sessionId).toBe("string") + expect(tool.state.title).toBe("inspect bug") + expect(tool.state.metadata?.model).toBeDefined() + + yield* prompt.cancel(chat.id) + yield* Fiber.await(fiber) + }), + { git: true, config: providerCfg }, + ), + 10_000, +) + +it.live( + "loop sets status to busy then idle", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const status = yield* SessionStatus.Service + + yield* llm.hang + + const chat = yield* sessions.create({}) + yield* user(chat.id, "hi") + + const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* llm.wait(1) + expect((yield* status.get(chat.id)).type).toBe("busy") + yield* prompt.cancel(chat.id) + yield* Fiber.await(fiber) + expect((yield* status.get(chat.id)).type).toBe("idle") + }), + { git: true, config: providerCfg }, + ), + 3_000, +) + +// Cancel semantics + +it.live( + "cancel interrupts loop and resolves with an assistant message", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Pinned" }) + yield* seed(chat.id) + + yield* llm.hang + + yield* user(chat.id, "more") + + const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* llm.wait(1) + yield* prompt.cancel(chat.id) + const exit = yield* Fiber.await(fiber) + expect(Exit.isSuccess(exit)).toBe(true) + if (Exit.isSuccess(exit)) { + expect(exit.value.info.role).toBe("assistant") + } + }), + { git: true, config: providerCfg }, + ), + 3_000, +) + +it.live( + "cancel records MessageAbortedError on interrupted process", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Pinned" }) + yield* llm.hang + yield* user(chat.id, "hello") + + const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* llm.wait(1) + yield* prompt.cancel(chat.id) + const exit = yield* Fiber.await(fiber) + expect(Exit.isSuccess(exit)).toBe(true) + if (Exit.isSuccess(exit)) { + const info = exit.value.info + if (info.role === "assistant") { + expect(info.error?.name).toBe("MessageAbortedError") + } + } + }), + { git: true, config: providerCfg }, + ), + 3_000, +) + +it.live( + "cancel finalizes subtask tool state", + () => + provideTmpdirInstance( + () => + Effect.gen(function* () { + const ready = defer() + const aborted = defer() + const registry = yield* ToolRegistry.Service + const { task } = yield* registry.named() + const original = task.execute + task.execute = (_args, ctx) => + Effect.callback((_resume) => { + ready.resolve() + ctx.abort.addEventListener("abort", () => aborted.resolve(), { once: true }) + return Effect.sync(() => aborted.resolve()) + }) + yield* Effect.addFinalizer(() => Effect.sync(() => void (task.execute = original))) + + const { prompt, chat } = yield* boot() + const msg = yield* user(chat.id, "hello") + yield* addSubtask(chat.id, msg.id) + + const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* Effect.promise(() => ready.promise) + yield* prompt.cancel(chat.id) + yield* Effect.promise(() => aborted.promise) + + const exit = yield* Fiber.await(fiber) + expect(Exit.isSuccess(exit)).toBe(true) + + const msgs = yield* MessageV2.filterCompactedEffect(chat.id) + const taskMsg = msgs.find((item) => item.info.role === "assistant" && item.info.agent === "general") + expect(taskMsg?.info.role).toBe("assistant") + if (!taskMsg || taskMsg.info.role !== "assistant") return + + const tool = toolPart(taskMsg.parts) + expect(tool?.type).toBe("tool") + if (!tool) return + + expect(tool.state.status).not.toBe("running") + expect(taskMsg.info.time.completed).toBeDefined() + expect(taskMsg.info.finish).toBeDefined() + }), + { git: true, config: cfg }, + ), + 30_000, +) + +it.live( + "cancel with queued callers resolves all cleanly", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Pinned" }) + yield* llm.hang + yield* user(chat.id, "hello") + + const a = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* llm.wait(1) + const b = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* Effect.sleep(50) + + yield* prompt.cancel(chat.id) + const [exitA, exitB] = yield* Effect.all([Fiber.await(a), Fiber.await(b)]) + expect(Exit.isSuccess(exitA)).toBe(true) + expect(Exit.isSuccess(exitB)).toBe(true) + if (Exit.isSuccess(exitA) && Exit.isSuccess(exitB)) { + expect(exitA.value.info.id).toBe(exitB.value.info.id) + } + }), + { git: true, config: providerCfg }, + ), + 3_000, +) + +// Queue semantics + +it.live("concurrent loop callers get same result", () => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const { prompt, run, chat } = yield* boot() + yield* seed(chat.id, { finish: "stop" }) + + const [a, b] = yield* Effect.all([prompt.loop({ sessionID: chat.id }), prompt.loop({ sessionID: chat.id })], { + concurrency: "unbounded", + }) + + expect(a.info.id).toBe(b.info.id) + expect(a.info.role).toBe("assistant") + yield* run.assertNotBusy(chat.id) + }), + { git: true }, + ), +) + +it.live( + "concurrent loop callers all receive same error result", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Pinned" }) + + yield* llm.fail("boom") + yield* user(chat.id, "hello") + + const [a, b] = yield* Effect.all([prompt.loop({ sessionID: chat.id }), prompt.loop({ sessionID: chat.id })], { + concurrency: "unbounded", + }) + expect(a.info.id).toBe(b.info.id) + expect(a.info.role).toBe("assistant") + }), + { git: true, config: providerCfg }, + ), + 3_000, +) + +it.live( + "prompt submitted during an active run is included in the next LLM input", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const gate = defer() + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Pinned" }) + + yield* llm.hold("first", gate.promise) + yield* llm.text("second") + + const a = yield* prompt + .prompt({ + sessionID: chat.id, + agent: "build", + model: ref, + parts: [{ type: "text", text: "first" }], + }) + .pipe(Effect.forkChild) + + yield* llm.wait(1) + + const id = MessageID.ascending() + const b = yield* prompt + .prompt({ + sessionID: chat.id, + messageID: id, + agent: "build", + model: ref, + parts: [{ type: "text", text: "second" }], + }) + .pipe(Effect.forkChild) + + yield* Effect.promise(async () => { + const end = Date.now() + 5000 + while (Date.now() < end) { + const msgs = await Effect.runPromise(sessions.messages({ sessionID: chat.id })) + if (msgs.some((msg) => msg.info.role === "user" && msg.info.id === id)) return + await new Promise((done) => setTimeout(done, 20)) + } + throw new Error("timed out waiting for second prompt to save") + }) + + gate.resolve() + + const [ea, eb] = yield* Effect.all([Fiber.await(a), Fiber.await(b)]) + expect(Exit.isSuccess(ea)).toBe(true) + expect(Exit.isSuccess(eb)).toBe(true) + expect(yield* llm.calls).toBe(2) + + const msgs = yield* sessions.messages({ sessionID: chat.id }) + const assistants = msgs.filter((msg) => msg.info.role === "assistant") + expect(assistants).toHaveLength(2) + const last = assistants.at(-1) + if (!last || last.info.role !== "assistant") throw new Error("expected second assistant") + expect(last.info.parentID).toBe(id) + expect(last.parts.some((part) => part.type === "text" && part.text === "second")).toBe(true) + + const inputs = yield* llm.inputs + expect(inputs).toHaveLength(2) + expect(JSON.stringify(inputs.at(-1)?.messages)).toContain("second") + }), + { git: true, config: providerCfg }, + ), + 3_000, +) + +it.live( + "assertNotBusy throws BusyError when loop running", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const run = yield* SessionRunState.Service + const sessions = yield* Session.Service + yield* llm.hang + + const chat = yield* sessions.create({}) + yield* user(chat.id, "hi") + + const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* llm.wait(1) + + const exit = yield* run.assertNotBusy(chat.id).pipe(Effect.exit) + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + expect(Cause.squash(exit.cause)).toBeInstanceOf(Session.BusyError) + } + + yield* prompt.cancel(chat.id) + yield* Fiber.await(fiber) + }), + { git: true, config: providerCfg }, + ), + 3_000, +) + +it.live("assertNotBusy succeeds when idle", () => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const run = yield* SessionRunState.Service + const sessions = yield* Session.Service + + const chat = yield* sessions.create({}) + const exit = yield* run.assertNotBusy(chat.id).pipe(Effect.exit) + expect(Exit.isSuccess(exit)).toBe(true) + }), + { git: true }, + ), +) + +// Shell semantics + +it.live( + "shell rejects with BusyError when loop running", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Pinned" }) + yield* llm.hang + yield* user(chat.id, "hi") + + const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* llm.wait(1) + + const exit = yield* prompt.shell({ sessionID: chat.id, agent: "build", command: "echo hi" }).pipe(Effect.exit) + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + expect(Cause.squash(exit.cause)).toBeInstanceOf(Session.BusyError) + } + + yield* prompt.cancel(chat.id) + yield* Fiber.await(fiber) + }), + { git: true, config: providerCfg }, + ), + 3_000, +) + +unix("shell captures stdout and stderr in completed tool output", () => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const { prompt, run, chat } = yield* boot() + const result = yield* prompt.shell({ + sessionID: chat.id, + agent: "build", + command: "printf out && printf err >&2", + }) + + expect(result.info.role).toBe("assistant") + const tool = completedTool(result.parts) + if (!tool) return + + expect(tool.state.output).toContain("out") + expect(tool.state.output).toContain("err") + expect(tool.state.metadata.output).toContain("out") + expect(tool.state.metadata.output).toContain("err") + yield* run.assertNotBusy(chat.id) + }), + { git: true, config: cfg }, + ), +) + +unix("shell completes a fast command on the preferred shell", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const { prompt, run, chat } = yield* boot() + const result = yield* prompt.shell({ + sessionID: chat.id, + agent: "build", + command: "pwd", + }) + + expect(result.info.role).toBe("assistant") + const tool = completedTool(result.parts) + if (!tool) return + + expect(tool.state.input.command).toBe("pwd") + expect(tool.state.output).toContain(dir) + expect(tool.state.metadata.output).toContain(dir) + yield* run.assertNotBusy(chat.id) + }), + { git: true, config: cfg }, + ), +) + +unix("shell lists files from the project directory", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const { prompt, run, chat } = yield* boot() + yield* Effect.promise(() => Bun.write(path.join(dir, "README.md"), "# e2e\n")) + + const result = yield* prompt.shell({ + sessionID: chat.id, + agent: "build", + command: "command ls", + }) + + expect(result.info.role).toBe("assistant") + const tool = completedTool(result.parts) + if (!tool) return + + expect(tool.state.input.command).toBe("command ls") + expect(tool.state.output).toContain("README.md") + expect(tool.state.metadata.output).toContain("README.md") + yield* run.assertNotBusy(chat.id) + }), + { git: true, config: cfg }, + ), +) + +unix("shell captures stderr from a failing command", () => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const { prompt, run, chat } = yield* boot() + const result = yield* prompt.shell({ + sessionID: chat.id, + agent: "build", + command: "command -v __nonexistent_cmd_e2e__ || echo 'not found' >&2; exit 1", + }) + + expect(result.info.role).toBe("assistant") + const tool = completedTool(result.parts) + if (!tool) return + + expect(tool.state.output).toContain("not found") + expect(tool.state.metadata.output).toContain("not found") + yield* run.assertNotBusy(chat.id) + }), + { git: true, config: cfg }, + ), +) + +unix( + "shell updates running metadata before process exit", + () => + withSh(() => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const { prompt, chat } = yield* boot() + + const fiber = yield* prompt + .shell({ sessionID: chat.id, agent: "build", command: "printf first && sleep 0.2 && printf second" }) + .pipe(Effect.forkChild) + + yield* Effect.promise(async () => { + const start = Date.now() + while (Date.now() - start < 5000) { + const msgs = await MessageV2.filterCompacted(MessageV2.stream(chat.id)) + const taskMsg = msgs.find((item) => item.info.role === "assistant") + const tool = taskMsg ? toolPart(taskMsg.parts) : undefined + if (tool?.state.status === "running" && tool.state.metadata?.output.includes("first")) return + await new Promise((done) => setTimeout(done, 20)) + } + throw new Error("timed out waiting for running shell metadata") + }) + + const exit = yield* Fiber.await(fiber) + expect(Exit.isSuccess(exit)).toBe(true) + }), + { git: true, config: cfg }, + ), + ), + 30_000, +) + +it.live( + "loop waits while shell runs and starts after shell exits", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ + title: "Pinned", + permission: [{ permission: "*", pattern: "*", action: "allow" }], + }) + yield* llm.text("after-shell") + + const sh = yield* prompt + .shell({ sessionID: chat.id, agent: "build", command: "sleep 0.2" }) + .pipe(Effect.forkChild) + yield* Effect.sleep(50) + + const loop = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* Effect.sleep(50) + + expect(yield* llm.calls).toBe(0) + + yield* Fiber.await(sh) + const exit = yield* Fiber.await(loop) + + expect(Exit.isSuccess(exit)).toBe(true) + if (Exit.isSuccess(exit)) { + expect(exit.value.info.role).toBe("assistant") + expect(exit.value.parts.some((part) => part.type === "text" && part.text === "after-shell")).toBe(true) + } + expect(yield* llm.calls).toBe(1) + }), + { git: true, config: providerCfg }, + ), + 3_000, +) + +it.live( + "shell completion resumes queued loop callers", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ + title: "Pinned", + permission: [{ permission: "*", pattern: "*", action: "allow" }], + }) + yield* llm.text("done") + + const sh = yield* prompt + .shell({ sessionID: chat.id, agent: "build", command: "sleep 0.2" }) + .pipe(Effect.forkChild) + yield* Effect.sleep(50) + + const a = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + const b = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* Effect.sleep(50) + + expect(yield* llm.calls).toBe(0) + + yield* Fiber.await(sh) + const [ea, eb] = yield* Effect.all([Fiber.await(a), Fiber.await(b)]) + + expect(Exit.isSuccess(ea)).toBe(true) + expect(Exit.isSuccess(eb)).toBe(true) + if (Exit.isSuccess(ea) && Exit.isSuccess(eb)) { + expect(ea.value.info.id).toBe(eb.value.info.id) + expect(ea.value.info.role).toBe("assistant") + } + expect(yield* llm.calls).toBe(1) + }), + { git: true, config: providerCfg }, + ), + 3_000, +) + +unix( + "cancel interrupts shell and resolves cleanly", + () => + withSh(() => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const { prompt, run, chat } = yield* boot() + + const sh = yield* prompt + .shell({ sessionID: chat.id, agent: "build", command: "sleep 30" }) + .pipe(Effect.forkChild) + yield* Effect.sleep(50) + + yield* prompt.cancel(chat.id) + + const status = yield* SessionStatus.Service + expect((yield* status.get(chat.id)).type).toBe("idle") + const busy = yield* run.assertNotBusy(chat.id).pipe(Effect.exit) + expect(Exit.isSuccess(busy)).toBe(true) + + const exit = yield* Fiber.await(sh) + expect(Exit.isSuccess(exit)).toBe(true) + if (Exit.isSuccess(exit)) { + expect(exit.value.info.role).toBe("assistant") + const tool = completedTool(exit.value.parts) + if (tool) { + expect(tool.state.output).toContain("User aborted the command") + } + } + }), + { git: true, config: cfg }, + ), + ), + 30_000, +) + +unix( + "cancel persists aborted shell result when shell ignores TERM", + () => + withSh(() => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const { prompt, chat } = yield* boot() + + const sh = yield* prompt + .shell({ sessionID: chat.id, agent: "build", command: "trap '' TERM; sleep 30" }) + .pipe(Effect.forkChild) + yield* Effect.sleep(50) + + yield* prompt.cancel(chat.id) + + const exit = yield* Fiber.await(sh) + expect(Exit.isSuccess(exit)).toBe(true) + if (Exit.isSuccess(exit)) { + expect(exit.value.info.role).toBe("assistant") + const tool = completedTool(exit.value.parts) + if (tool) { + expect(tool.state.output).toContain("User aborted the command") + } + } + }), + { git: true, config: cfg }, + ), + ), + 30_000, +) + +unix( + "cancel finalizes interrupted bash tool output through normal truncation", + () => + provideTmpdirServer( + ({ dir, llm }) => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ + title: "Interrupted bash truncation", + permission: [{ permission: "*", pattern: "*", action: "allow" }], + }) + + yield* prompt.prompt({ + sessionID: chat.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "run bash" }], + }) + + yield* llm.tool("bash", { + command: + 'i=0; while [ "$i" -lt 4000 ]; do printf "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx %05d\\n" "$i"; i=$((i + 1)); done; sleep 30', + description: "Print many lines", + timeout: 30_000, + workdir: path.resolve(dir), + }) + + const run = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* llm.wait(1) + yield* Effect.sleep(150) + yield* prompt.cancel(chat.id) + + const exit = yield* Fiber.await(run) + expect(Exit.isSuccess(exit)).toBe(true) + if (Exit.isFailure(exit)) return + + const tool = completedTool(exit.value.parts) + if (!tool) return + + expect(tool.state.metadata.truncated).toBe(true) + expect(typeof tool.state.metadata.outputPath).toBe("string") + expect(tool.state.output).toMatch(/\.\.\.output truncated\.\.\./) + expect(tool.state.output).toMatch(/Full output saved to:\s+\S+/) + expect(tool.state.output).not.toContain("Tool execution aborted") + }), + { git: true, config: providerCfg }, + ), + 30_000, +) + +unix( + "cancel interrupts loop queued behind shell", + () => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const { prompt, chat } = yield* boot() + + const sh = yield* prompt + .shell({ sessionID: chat.id, agent: "build", command: "sleep 30" }) + .pipe(Effect.forkChild) + yield* Effect.sleep(50) + + const loop = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) + yield* Effect.sleep(50) + + yield* prompt.cancel(chat.id) + + const exit = yield* Fiber.await(loop) + expect(Exit.isSuccess(exit)).toBe(true) + + yield* Fiber.await(sh) + }), + { git: true, config: cfg }, + ), + 30_000, +) + +unix( + "shell rejects when another shell is already running", + () => + withSh(() => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const { prompt, chat } = yield* boot() + + const a = yield* prompt + .shell({ sessionID: chat.id, agent: "build", command: "sleep 30" }) + .pipe(Effect.forkChild) + yield* Effect.sleep(50) + + const exit = yield* prompt + .shell({ sessionID: chat.id, agent: "build", command: "echo hi" }) + .pipe(Effect.exit) + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + expect(Cause.squash(exit.cause)).toBeInstanceOf(Session.BusyError) + } + + yield* prompt.cancel(chat.id) + yield* Fiber.await(a) + }), + { git: true, config: cfg }, + ), + ), + 30_000, +) + +// Abort signal propagation tests for inline tool execution + +/** Override a tool's execute to hang until aborted. Returns ready/aborted defers and a finalizer. */ +function hangUntilAborted(tool: { execute: (...args: any[]) => any }) { + const ready = defer() + const aborted = defer() + const original = tool.execute + tool.execute = (_args: any, ctx: any) => { + ready.resolve() + ctx.abort.addEventListener("abort", () => aborted.resolve(), { once: true }) + return Effect.callback(() => {}) + } + const restore = Effect.addFinalizer(() => Effect.sync(() => void (tool.execute = original))) + return { ready, aborted, restore } +} + +it.live( + "interrupt propagates abort signal to read tool via file part (text/plain)", + () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const registry = yield* ToolRegistry.Service + const { read } = yield* registry.named() + const { ready, aborted, restore } = hangUntilAborted(read) + yield* restore + + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Abort Test" }) + + const testFile = path.join(dir, "test.txt") + yield* Effect.promise(() => Bun.write(testFile, "hello world")) + + const fiber = yield* prompt + .prompt({ + sessionID: chat.id, + agent: "build", + parts: [ + { type: "text", text: "read this" }, + { type: "file", url: `file://${testFile}`, filename: "test.txt", mime: "text/plain" }, + ], + }) + .pipe(Effect.forkChild) + + yield* Effect.promise(() => ready.promise) + yield* Fiber.interrupt(fiber) + + yield* Effect.promise(() => + Promise.race([ + aborted.promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error("abort signal not propagated within 2s")), 2_000), + ), + ]), + ) + }), + { git: true, config: cfg }, + ), + 30_000, +) + +it.live( + "interrupt propagates abort signal to read tool via file part (directory)", + () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const registry = yield* ToolRegistry.Service + const { read } = yield* registry.named() + const { ready, aborted, restore } = hangUntilAborted(read) + yield* restore + + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Abort Test" }) + + const fiber = yield* prompt + .prompt({ + sessionID: chat.id, + agent: "build", + parts: [ + { type: "text", text: "read this" }, + { type: "file", url: `file://${dir}`, filename: "dir", mime: "application/x-directory" }, + ], + }) + .pipe(Effect.forkChild) + + yield* Effect.promise(() => ready.promise) + yield* Fiber.interrupt(fiber) + + yield* Effect.promise(() => + Promise.race([ + aborted.promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error("abort signal not propagated within 2s")), 2_000), + ), + ]), + ) + }), + { git: true, config: cfg }, + ), + 30_000, +) + +// Missing file handling + +it.live("does not fail the prompt when a file part is missing", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({}) + + const missing = path.join(dir, "does-not-exist.ts") + const msg = yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + noReply: true, + parts: [ + { type: "text", text: "please review @does-not-exist.ts" }, + { + type: "file", + mime: "text/plain", + url: `file://${missing}`, + filename: "does-not-exist.ts", + }, + ], + }) + + if (msg.info.role !== "user") throw new Error("expected user message") + const hasFailure = msg.parts.some( + (part) => part.type === "text" && part.synthetic && part.text.includes("Read tool failed to read"), + ) + expect(hasFailure).toBe(true) + + yield* sessions.remove(session.id) + }), + { git: true, config: cfg }, + ), +) + +it.live("keeps stored part order stable when file resolution is async", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({}) + + const missing = path.join(dir, "still-missing.ts") + const msg = yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + noReply: true, + parts: [ + { + type: "file", + mime: "text/plain", + url: `file://${missing}`, + filename: "still-missing.ts", + }, + { type: "text", text: "after-file" }, + ], + }) + + if (msg.info.role !== "user") throw new Error("expected user message") + + const stored = MessageV2.get({ + sessionID: session.id, + messageID: msg.info.id, + }) + const text = stored.parts.filter((part) => part.type === "text").map((part) => part.text) + + expect(text[0]?.startsWith("Called the Read tool with the following input:")).toBe(true) + expect(text[1]?.includes("Read tool failed to read")).toBe(true) + expect(text[2]).toBe("after-file") + + yield* sessions.remove(session.id) + }), + { git: true, config: cfg }, + ), +) + +// Special characters in filenames + +it.live("handles filenames with # character", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + yield* Effect.promise(() => Bun.write(path.join(dir, "file#name.txt"), "special content\n")) + + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({}) + const parts = yield* prompt.resolvePromptParts("Read @file#name.txt") + const fileParts = parts.filter((part) => part.type === "file") + + expect(fileParts.length).toBe(1) + expect(fileParts[0].filename).toBe("file#name.txt") + expect(fileParts[0].url).toContain("%23") + + const decodedPath = fileURLToPath(fileParts[0].url) + expect(decodedPath).toBe(path.join(dir, "file#name.txt")) + + const message = yield* prompt.prompt({ + sessionID: session.id, + parts, + noReply: true, + }) + const stored = MessageV2.get({ sessionID: session.id, messageID: message.info.id }) + const textParts = stored.parts.filter((part) => part.type === "text") + const hasContent = textParts.some((part) => part.text.includes("special content")) + expect(hasContent).toBe(true) + + yield* sessions.remove(session.id) + }), + { git: true, config: cfg }, + ), +) + +// Regression: empty assistant turn loop + +it.live("does not loop empty assistant turns for a simple reply", () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({ title: "Prompt regression" }) + + yield* llm.text("packages/opencode/src/session/processor.ts") + + const result = yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + parts: [{ type: "text", text: "Where is SessionProcessor?" }], }) - await Instance.provide({ - directory: tmp.path, - fn: () => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({ title: "Prompt cancel regression" }) - const task = Effect.runPromise( - prompt.prompt({ - sessionID: session.id, - agent: "build", - parts: [{ type: "text", text: "Cancel me" }], - }), - ) + expect(result.info.role).toBe("assistant") + expect(result.parts.some((part) => part.type === "text" && part.text.includes("processor.ts"))).toBe(true) - yield* Effect.promise(() => ready.promise) - yield* prompt.cancel(session.id) + const msgs = yield* sessions.messages({ sessionID: session.id }) + expect(msgs.filter((msg) => msg.info.role === "assistant")).toHaveLength(1) + expect(yield* llm.calls).toBe(1) + }), + { git: true, config: providerCfg }, + ), +) - const result = yield* Effect.promise(() => - Promise.race([ - task, - new Promise((_, reject) => - setTimeout(() => reject(new Error("timed out waiting for cancel")), 1000), - ), - ]), - ) +it.live( + "records aborted errors when prompt is cancelled mid-stream", + () => + provideTmpdirServer( + Effect.fnUntraced(function* ({ llm }) { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({ title: "Prompt cancel regression" }) - expect(result.info.role).toBe("assistant") - if (result.info.role === "assistant") { - expect(result.info.error?.name).toBe("MessageAbortedError") - } + yield* llm.hang - const msgs = yield* sessions.messages({ sessionID: session.id }) - const last = msgs.findLast((msg) => msg.info.role === "assistant") - expect(last?.info.role).toBe("assistant") - if (last?.info.role === "assistant") { - expect(last.info.error?.name).toBe("MessageAbortedError") - } - }), - ), - }) - } finally { - void server.stop(true) - } - }) -}) + const fiber = yield* prompt + .prompt({ + sessionID: session.id, + agent: "build", + parts: [{ type: "text", text: "Cancel me" }], + }) + .pipe(Effect.forkChild) -describe("session.prompt agent variant", () => { - test("applies agent variant only when using agent model", async () => { - const prev = process.env.OPENAI_API_KEY - process.env.OPENAI_API_KEY = "test-openai-key" + yield* llm.wait(1) + yield* prompt.cancel(session.id) - try { - await using tmp = await tmpdir({ - git: true, - config: { - agent: { - build: { - model: "openai/gpt-5.2", - variant: "xhigh", + const exit = yield* Fiber.await(fiber) + expect(Exit.isSuccess(exit)).toBe(true) + if (Exit.isSuccess(exit)) { + expect(exit.value.info.role).toBe("assistant") + if (exit.value.info.role === "assistant") { + expect(exit.value.info.error?.name).toBe("MessageAbortedError") + } + } + + const msgs = yield* sessions.messages({ sessionID: session.id }) + const last = msgs.findLast((msg) => msg.info.role === "assistant") + expect(last?.info.role).toBe("assistant") + if (last?.info.role === "assistant") { + expect(last.info.error?.name).toBe("MessageAbortedError") + } + }), + { git: true, config: providerCfg }, + ), + 3_000, +) + +// Agent variant + +it.live("applies agent variant only when using agent model", () => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({}) + + const other = yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + model: { providerID: ProviderID.make("opencode"), modelID: ModelID.make("kimi-k2.5-free") }, + noReply: true, + parts: [{ type: "text", text: "hello" }], + }) + if (other.info.role !== "user") throw new Error("expected user message") + expect(other.info.model.variant).toBeUndefined() + + const match = yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "hello again" }], + }) + if (match.info.role !== "user") throw new Error("expected user message") + expect(match.info.model).toEqual({ + providerID: ProviderID.make("test"), + modelID: ModelID.make("test-model"), + variant: "xhigh", + }) + expect(match.info.model.variant).toBe("xhigh") + + const override = yield* prompt.prompt({ + sessionID: session.id, + agent: "build", + noReply: true, + variant: "high", + parts: [{ type: "text", text: "hello third" }], + }) + if (override.info.role !== "user") throw new Error("expected user message") + expect(override.info.model.variant).toBe("high") + + yield* sessions.remove(session.id) + }), + { + git: true, + config: { + ...cfg, + provider: { + ...cfg.provider, + test: { + ...cfg.provider.test, + models: { + "test-model": { + ...cfg.provider.test.models["test-model"], + variants: { xhigh: {}, high: {} }, + }, }, }, }, - }) + agent: { + build: { + model: "test/test-model", + variant: "xhigh", + }, + }, + }, + }, + ), +) - await Instance.provide({ - directory: tmp.path, - fn: () => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({}) +// Agent / command resolution errors - const other = yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - model: { providerID: ProviderID.make("opencode"), modelID: ModelID.make("kimi-k2.5-free") }, - noReply: true, - parts: [{ type: "text", text: "hello" }], - }) - if (other.info.role !== "user") throw new Error("expected user message") - expect(other.info.model.variant).toBeUndefined() +it.live( + "unknown agent throws typed error", + () => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({}) + const exit = yield* prompt + .prompt({ + sessionID: session.id, + agent: "nonexistent-agent-xyz", + noReply: true, + parts: [{ type: "text", text: "hello" }], + }) + .pipe(Effect.exit) - const match = yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - noReply: true, - parts: [{ type: "text", text: "hello again" }], - }) - if (match.info.role !== "user") throw new Error("expected user message") - expect(match.info.model).toEqual({ - providerID: ProviderID.make("openai"), - modelID: ModelID.make("gpt-5.2"), - variant: "xhigh", - }) - expect(match.info.model.variant).toBe("xhigh") - - const override = yield* prompt.prompt({ - sessionID: session.id, - agent: "build", - noReply: true, - variant: "high", - parts: [{ type: "text", text: "hello third" }], - }) - if (override.info.role !== "user") throw new Error("expected user message") - expect(override.info.model.variant).toBe("high") - - yield* sessions.remove(session.id) - }), - ), - }) - } finally { - if (prev === undefined) delete process.env.OPENAI_API_KEY - else process.env.OPENAI_API_KEY = prev - } - }) -}) - -describe("session.agent-resolution", () => { - test("unknown agent throws typed error", async () => { - await using tmp = await tmpdir({ git: true }) - await Instance.provide({ - directory: tmp.path, - fn: () => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({}) - const err = yield* Effect.promise(() => - Effect.runPromise( - prompt.prompt({ - sessionID: session.id, - agent: "nonexistent-agent-xyz", - noReply: true, - parts: [{ type: "text", text: "hello" }], - }), - ).then( - () => undefined, - (e) => e, - ), - ) - expect(err).toBeDefined() + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + const err = Cause.squash(exit.cause) expect(err).not.toBeInstanceOf(TypeError) expect(NamedError.Unknown.isInstance(err)).toBe(true) if (NamedError.Unknown.isInstance(err)) { expect(err.data.message).toContain('Agent not found: "nonexistent-agent-xyz"') } - }), - ), - }) - }, 30000) + } + }), + { git: true }, + ), + 30_000, +) - test("unknown agent error includes available agent names", async () => { - await using tmp = await tmpdir({ git: true }) - await Instance.provide({ - directory: tmp.path, - fn: () => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({}) - const err = yield* Effect.promise(() => - Effect.runPromise( - prompt.prompt({ - sessionID: session.id, - agent: "nonexistent-agent-xyz", - noReply: true, - parts: [{ type: "text", text: "hello" }], - }), - ).then( - () => undefined, - (e) => e, - ), - ) +it.live( + "unknown agent error includes available agent names", + () => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({}) + const exit = yield* prompt + .prompt({ + sessionID: session.id, + agent: "nonexistent-agent-xyz", + noReply: true, + parts: [{ type: "text", text: "hello" }], + }) + .pipe(Effect.exit) + + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + const err = Cause.squash(exit.cause) expect(NamedError.Unknown.isInstance(err)).toBe(true) if (NamedError.Unknown.isInstance(err)) { expect(err.data.message).toContain("build") } - }), - ), - }) - }, 30000) + } + }), + { git: true }, + ), + 30_000, +) - test("unknown command throws typed error with available names", async () => { - await using tmp = await tmpdir({ git: true }) - await Instance.provide({ - directory: tmp.path, - fn: () => - run( - Effect.gen(function* () { - const prompt = yield* SessionPrompt.Service - const sessions = yield* Session.Service - const session = yield* sessions.create({}) - const err = yield* Effect.promise(() => - Effect.runPromise( - prompt.command({ - sessionID: session.id, - command: "nonexistent-command-xyz", - arguments: "", - }), - ).then( - () => undefined, - (e) => e, - ), - ) - expect(err).toBeDefined() +it.live( + "unknown command throws typed error with available names", + () => + provideTmpdirInstance( + (_dir) => + Effect.gen(function* () { + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const session = yield* sessions.create({}) + const exit = yield* prompt + .command({ + sessionID: session.id, + command: "nonexistent-command-xyz", + arguments: "", + }) + .pipe(Effect.exit) + + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + const err = Cause.squash(exit.cause) expect(err).not.toBeInstanceOf(TypeError) expect(NamedError.Unknown.isInstance(err)).toBe(true) if (NamedError.Unknown.isInstance(err)) { expect(err.data.message).toContain('Command not found: "nonexistent-command-xyz"') expect(err.data.message).toContain("init") } - }), - ), - }) - }, 30000) -}) + } + }), + { git: true }, + ), + 30_000, +) From 96a534d8c639db794012281b5419e727df538e97 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 12:05:23 -0400 Subject: [PATCH 019/426] feat(core): bridge GET /config through experimental HttpApi (#23712) --- packages/opencode/specs/effect/http-api.md | 16 +++++------ packages/opencode/src/config/agent.ts | 3 ++- packages/opencode/src/config/config.ts | 2 +- packages/opencode/src/config/mcp.ts | 27 ++++++++++--------- packages/opencode/src/config/provider.ts | 9 ++++--- packages/opencode/src/config/server.ts | 10 ++++--- .../server/routes/instance/httpapi/config.ts | 22 ++++++++++++--- .../src/server/routes/instance/index.ts | 1 + 8 files changed, 56 insertions(+), 34 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 93ef81a32..d882857ba 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -224,7 +224,7 @@ When to use each: Promoting a previously-anonymous schema to Schema.Class is acceptable when it is top-level or endpoint-facing, but call it out in the PR — it is an additive SDK change (`export type Foo = ...` newly appears) even if it preserves the JSON shape. -Schemas that are **not** pure objects (enums, unions, records, tuples) cannot use Schema.Class. For those, add `.annotate({ identifier: "FooName" })` to get the same named-ref behavior: +Schemas that are **not** pure objects (enums, unions, records, tuples) cannot use Schema.Class. For those — and for pure-object schemas where handlers populate plain objects rather than class instances — add `.annotate({ identifier: "FooName" })` to get the same named-ref behavior without the `instanceof` requirement: ```ts export const Action = Schema.Literals(["ask", "allow", "deny"]).annotate({ identifier: "PermissionActionConfig" }) @@ -373,9 +373,9 @@ The first slice is successful if: - `Schema.Class` works well for route DTOs such as `Question.Request`, `Question.Info`, and `Question.Reply`. - scalar or collection schemas such as `Question.Answer` should stay as schemas and use helpers like `withStatics(...)` instead of being forced into classes. -- if an `HttpApi` success schema uses `Schema.Class`, the handler or underlying service needs to return real schema instances rather than plain objects. +- if an `HttpApi` success schema uses `Schema.Class`, the handler or underlying service needs to return real schema instances rather than plain objects. `Schema.Class`'s Declaration AST enforces `input instanceof self || input.[ClassTypeId]` during encode (see effect-smol `Schema.ts:10479-10484`). Plain objects from zod parse fail with `Expected Foo, got {...}`. This surfaced on `GET /config` where the service returns zod-parsed plain objects and `Config.InfoSchema` referenced `ConfigProvider.Info` (class). The fix was to convert pure-object classes to `Schema.Struct(...).annotate({ identifier: "..." })` — same named SDK `$ref`, no instance requirement. Verified byte-identical `types.gen.ts` vs `dev`. - internal event payloads can stay anonymous when we want to avoid adding extra named OpenAPI component churn for non-route shapes. -- `Schema.Class` emits named `$ref` in OpenAPI — only use it for types that already had `.meta({ ref })` in the old Zod schema. Inner/nested types should stay as `Schema.Struct` to avoid SDK shape changes. +- `Schema.Class` emits named `$ref` in OpenAPI — only use it for types that already had `.meta({ ref })` in the old Zod schema **and** when the handler/service returns real instances. For schemas that need a named `$ref` but are populated from plain objects, use `Schema.Struct(...).annotate({ identifier: "..." })` instead. Inner/nested types should stay as `Schema.Struct` to avoid SDK shape changes. ### Integration @@ -404,8 +404,7 @@ Current instance route inventory: - `provider` - `bridged` endpoints: `GET /provider`, `GET /provider/auth`, `POST /provider/:providerID/oauth/authorize`, `POST /provider/:providerID/oauth/callback` - `config` - `bridged` (partial) - bridged endpoint: `GET /config/providers` - later endpoint: `GET /config` + bridged endpoints: `GET /config`, `GET /config/providers` defer `PATCH /config` for now - `project` - `bridged` (partial) bridged endpoints: `GET /project`, `GET /project/current` @@ -431,9 +430,8 @@ Current instance route inventory: Recommended near-term sequence: 1. `workspace` read endpoints (`GET /experimental/workspace/adaptor`, `GET /experimental/workspace`, `GET /experimental/workspace/status`) -2. `config` full read endpoint (`GET /config`) -3. `file` JSON read endpoints -4. `mcp` JSON read endpoints +2. `file` JSON read endpoints +3. `mcp` JSON read endpoints ## Checklist @@ -449,8 +447,8 @@ Recommended near-term sequence: - [x] port remaining provider endpoints (`GET /provider`, OAuth mutations) - [x] port `config` providers read endpoint - [x] port `project` read endpoints (`GET /project`, `GET /project/current`) +- [x] port `GET /config` full read endpoint - [ ] port `workspace` read endpoints -- [ ] port `GET /config` full read endpoint - [ ] port `file` JSON read endpoints - [ ] decide when to remove the flag and make Effect routes the default diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index 85a214e12..9755c2037 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -101,7 +101,8 @@ const normalize = (agent: z.infer) => { } globalThis.Object.assign(permission, agent.permission) - return { ...agent, options, permission, steps: agent.steps ?? agent.maxSteps } + const steps = agent.steps ?? agent.maxSteps + return { ...agent, options, permission, ...(steps !== undefined ? { steps } : {}) } } export const Info = zod(AgentSchema).transform(normalize).meta({ ref: "AgentConfig" }) as unknown as z.ZodType< diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index cbe7cf7a2..7fe337176 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -91,7 +91,7 @@ const LogLevelRef = Schema.Any.annotate({ [ZodOverride]: Log.Level }) const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) const NonNegativeInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) -const InfoSchema = Schema.Struct({ +export const InfoSchema = Schema.Struct({ $schema: Schema.optional(Schema.String).annotate({ description: "JSON schema reference for configuration validation", }), diff --git a/packages/opencode/src/config/mcp.ts b/packages/opencode/src/config/mcp.ts index 8b77bc4c2..0887fa984 100644 --- a/packages/opencode/src/config/mcp.ts +++ b/packages/opencode/src/config/mcp.ts @@ -2,7 +2,7 @@ import { Schema } from "effect" import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" -export class Local extends Schema.Class("McpLocalConfig")({ +export const Local = Schema.Struct({ type: Schema.Literal("local").annotate({ description: "Type of MCP server connection" }), command: Schema.mutable(Schema.Array(Schema.String)).annotate({ description: "Command and arguments to run the MCP server", @@ -16,11 +16,12 @@ export class Local extends Schema.Class("McpLocalConfig")({ timeout: Schema.optional(Schema.Number).annotate({ description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.", }), -}) { - static readonly zod = zod(this) -} +}) + .annotate({ identifier: "McpLocalConfig" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Local = Schema.Schema.Type -export class OAuth extends Schema.Class("McpOAuthConfig")({ +export const OAuth = Schema.Struct({ clientId: Schema.optional(Schema.String).annotate({ description: "OAuth client ID. If not provided, dynamic client registration (RFC 7591) will be attempted.", }), @@ -31,11 +32,12 @@ export class OAuth extends Schema.Class("McpOAuthConfig")({ redirectUri: Schema.optional(Schema.String).annotate({ description: "OAuth redirect URI (default: http://127.0.0.1:19876/mcp/oauth/callback).", }), -}) { - static readonly zod = zod(this) -} +}) + .annotate({ identifier: "McpOAuthConfig" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type OAuth = Schema.Schema.Type -export class Remote extends Schema.Class("McpRemoteConfig")({ +export const Remote = Schema.Struct({ type: Schema.Literal("remote").annotate({ description: "Type of MCP server connection" }), url: Schema.String.annotate({ description: "URL of the remote MCP server" }), enabled: Schema.optional(Schema.Boolean).annotate({ @@ -50,9 +52,10 @@ export class Remote extends Schema.Class("McpRemoteConfig")({ timeout: Schema.optional(Schema.Number).annotate({ description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.", }), -}) { - static readonly zod = zod(this) -} +}) + .annotate({ identifier: "McpRemoteConfig" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Remote = Schema.Schema.Type export const Info = Schema.Union([Local, Remote]) .annotate({ discriminator: "type" }) diff --git a/packages/opencode/src/config/provider.ts b/packages/opencode/src/config/provider.ts index 212e71625..bd6ae3599 100644 --- a/packages/opencode/src/config/provider.ts +++ b/packages/opencode/src/config/provider.ts @@ -70,7 +70,7 @@ export const Model = Schema.Struct({ ), }).pipe(withStatics((s) => ({ zod: zod(s) }))) -export class Info extends Schema.Class("ProviderConfig")({ +export const Info = Schema.Struct({ api: Schema.optional(Schema.String), name: Schema.optional(Schema.String), env: Schema.optional(Schema.mutable(Schema.Array(Schema.String))), @@ -107,8 +107,9 @@ export class Info extends Schema.Class("ProviderConfig")({ ), ), models: Schema.optional(Schema.Record(Schema.String, Model)), -}) { - static readonly zod = zod(this) -} +}) + .annotate({ identifier: "ProviderConfig" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Schema.Schema.Type export * as ConfigProvider from "./provider" diff --git a/packages/opencode/src/config/server.ts b/packages/opencode/src/config/server.ts index 969a79964..3ce4fe626 100644 --- a/packages/opencode/src/config/server.ts +++ b/packages/opencode/src/config/server.ts @@ -1,7 +1,8 @@ import { Schema } from "effect" import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" -export class Server extends Schema.Class("ServerConfig")({ +export const Server = Schema.Struct({ port: Schema.optional(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0))).annotate({ description: "Port to listen on", }), @@ -13,8 +14,9 @@ export class Server extends Schema.Class("ServerConfig")({ cors: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({ description: "Additional domains to allow for CORS", }), -}) { - static readonly zod = zod(this) -} +}) + .annotate({ identifier: "ServerConfig" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Server = Schema.Schema.Type export * as ConfigServer from "./server" diff --git a/packages/opencode/src/server/routes/instance/httpapi/config.ts b/packages/opencode/src/server/routes/instance/httpapi/config.ts index 14aa94f9f..678e96e33 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/config.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/config.ts @@ -9,6 +9,15 @@ export const ConfigApi = HttpApi.make("config") .add( HttpApiGroup.make("config") .add( + HttpApiEndpoint.get("get", root, { + success: Config.InfoSchema, + }).annotateMerge( + OpenApi.annotations({ + identifier: "config.get", + summary: "Get configuration", + description: "Retrieve the current OpenCode configuration settings and preferences.", + }), + ), HttpApiEndpoint.get("providers", `${root}/providers`, { success: Provider.ConfigProvidersResult, }).annotateMerge( @@ -36,16 +45,23 @@ export const ConfigApi = HttpApi.make("config") export const configHandlers = Layer.unwrap( Effect.gen(function* () { - const svc = yield* Provider.Service + const providerSvc = yield* Provider.Service + const configSvc = yield* Config.Service + + const get = Effect.fn("ConfigHttpApi.get")(function* () { + return yield* configSvc.get() + }) const providers = Effect.fn("ConfigHttpApi.providers")(function* () { - const providers = yield* svc.list() + const providers = yield* providerSvc.list() return { providers: Object.values(providers), default: Provider.defaultModelIDs(providers), } }) - return HttpApiBuilder.group(ConfigApi, "config", (handlers) => handlers.handle("providers", providers)) + return HttpApiBuilder.group(ConfigApi, "config", (handlers) => + handlers.handle("get", get).handle("providers", providers), + ) }), ).pipe(Layer.provide(Provider.defaultLayer), Layer.provide(Config.defaultLayer)) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 5cc51d27a..0038c5961 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -40,6 +40,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.post("/question/:requestID/reject", (c) => handler(c.req.raw, context)) app.get("/permission", (c) => handler(c.req.raw, context)) app.post("/permission/:requestID/reply", (c) => handler(c.req.raw, context)) + app.get("/config", (c) => handler(c.req.raw, context)) app.get("/config/providers", (c) => handler(c.req.raw, context)) app.get("/provider", (c) => handler(c.req.raw, context)) app.get("/provider/auth", (c) => handler(c.req.raw, context)) From e95474df05d7054f905dc9294148e5e425f2e656 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:08:12 -0400 Subject: [PATCH 020/426] fix: revert parts of a824064c4 which caused system theme regression (#23714) --- bun.lock | 28 ++++++------- packages/opencode/package.json | 4 +- packages/opencode/src/cli/cmd/tui/app.tsx | 8 +++- .../opencode/src/cli/cmd/tui/util/terminal.ts | 39 +++++++++++++++++++ packages/plugin/package.json | 8 ++-- 5 files changed, 66 insertions(+), 21 deletions(-) diff --git a/bun.lock b/bun.lock index 0ba00b23f..ff186b675 100644 --- a/bun.lock +++ b/bun.lock @@ -367,8 +367,8 @@ "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", - "@opentui/core": "0.1.101", - "@opentui/solid": "0.1.101", + "@opentui/core": "0.1.99", + "@opentui/solid": "0.1.99", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -466,16 +466,16 @@ "zod": "catalog:", }, "devDependencies": { - "@opentui/core": "0.1.101", - "@opentui/solid": "0.1.101", + "@opentui/core": "0.1.99", + "@opentui/solid": "0.1.99", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "@typescript/native-preview": "catalog:", "typescript": "catalog:", }, "peerDependencies": { - "@opentui/core": ">=0.1.101", - "@opentui/solid": ">=0.1.101", + "@opentui/core": ">=0.1.99", + "@opentui/solid": ">=0.1.99", }, "optionalPeers": [ "@opentui/core", @@ -1604,21 +1604,21 @@ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], - "@opentui/core": ["@opentui/core@0.1.101", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.101", "@opentui/core-darwin-x64": "0.1.101", "@opentui/core-linux-arm64": "0.1.101", "@opentui/core-linux-x64": "0.1.101", "@opentui/core-win32-arm64": "0.1.101", "@opentui/core-win32-x64": "0.1.101", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-8jUhNKnwCDO3Y2iiEmagoQLjgX5l1WbddQiwky8B5JU4FW0/WRHairBmU1kRAQBmhdeg57dVinSG4iu2PAtKEA=="], + "@opentui/core": ["@opentui/core@0.1.99", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.99", "@opentui/core-darwin-x64": "0.1.99", "@opentui/core-linux-arm64": "0.1.99", "@opentui/core-linux-x64": "0.1.99", "@opentui/core-win32-arm64": "0.1.99", "@opentui/core-win32-x64": "0.1.99", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-I3+AEgGzqNWIpWX9g2WOscSPwtQDNOm4KlBjxBWCZjLxkF07u77heWXF7OiAdhKLtNUW6TFiyt6yznqAZPdG3A=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.101", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HtqZh8TIKCH1Nge5J0etBCpzYfPY4fVcq110uJm2As6D/dTTPv8r4J+KkrqoSphkpj/Y2b4t7KpqNHthXA0EVw=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.99", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bzVrqeX2vb5iWrc/ftOUOqeUY8XO+qSgoTwj5TXHuwagavgwD3Hpeyjx8+icnTTeM4pao0som1WR9xfye6/X5Q=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.101", "", { "os": "darwin", "cpu": "x64" }, "sha512-o5ClQWnGG1inRE2YZAatPw1jPEAJni00amcoIfKBj8e1WS+fQA+iQTq1xFunNcyNPObLDCVuW1X+NrbK9xmPvQ=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.99", "", { "os": "darwin", "cpu": "x64" }, "sha512-VE4FrXBYpkxnvkqcCV1a8aN9jyyMJMihVW+V2NLCtp+4yQsj0AapG5TiUSN76XnmSZRptxDy5rBmEempeoIZbg=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.101", "", { "os": "linux", "cpu": "arm64" }, "sha512-E/weY7DQpaPWGYDPD0CROHowUotqnVlk7Kb6l9+iZCrxm9s7HPRHkcMDVmcWDqHEqa/J879EJcqaUDzDArqC+w=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.99", "", { "os": "linux", "cpu": "arm64" }, "sha512-viXQsbpS7yHjYkl7+am32JdvG96QU9lvHh1UiZtpOxcNUUqiYmA2ZwZFPD2Bi54jNyj5l2hjH6YkD3DzE2FEWA=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.101", "", { "os": "linux", "cpu": "x64" }, "sha512-+Bfr8jLbbR1WREUMCCvSZ44G1+WU2lPqJx7x1StTa9iFNEdicxCdd0QQsO6cnKn5yW+2Pr/FdrqHbxSQw3ejbA=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.99", "", { "os": "linux", "cpu": "x64" }, "sha512-WLoEFINOSp0tZSR9y4LUuGc7n4Y7H1wcpjUPzQ9vChkYDXrfZltEanzoDWbDcQ4kZQW5tHVC7LrZHpAsRLwFZg=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.101", "", { "os": "win32", "cpu": "arm64" }, "sha512-LTMIHJzJrVqS8mgpp+tuyVHuqYlicQTvFi/sTsJ6Xswf1asatsvZYsbQByhBLpFT80j10G7uvDa361S5gjCUDA=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.99", "", { "os": "win32", "cpu": "arm64" }, "sha512-yWMOLWCEO8HdrctU1dMkgZC8qGkiO4Dwr4/e11tTvVpRmYhDsP/IR89ZjEEtOwnKwFOFuB/MxvflqaEWVQ2g5Q=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.101", "", { "os": "win32", "cpu": "x64" }, "sha512-VaMs5bg6y0tYKptaEK8Hy5wTp4m//wJRKUdW8uvrS9cFgxyovZGuw0+TfK3NgbdeX+8jWm8LEAiak4jle5BABg=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.99", "", { "os": "win32", "cpu": "x64" }, "sha512-aYRlsL2w8YRL6vPd7/hrqlNVkXU3QowWb01TOvAcHS8UAsXaGFUr47kSDyjxDi1wg1MzmVduCfsC7T3NoThV1w=="], - "@opentui/solid": ["@opentui/solid@0.1.101", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.101", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-STY2FQYtVS2rhUgpslG6mM0EAkgobBDF91+B+SNmvXIkJwP+ydP6UVgcuIo5McIbb9GIbAODx5X2Q48PSR7hgw=="], + "@opentui/solid": ["@opentui/solid@0.1.99", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.99", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-DrqqO4h2V88FmeIP2cErYkMU0ZK5MrUsZw3w6IzZpoXyyiL4/9qpWzUq+CXx+r16VP2iGxDJwGKUmtFAzUch2Q=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 5d8fd4b54..199f4b215 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -123,8 +123,8 @@ "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", - "@opentui/core": "0.1.101", - "@opentui/solid": "0.1.101", + "@opentui/core": "0.1.99", + "@opentui/solid": "0.1.99", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 5da2740cc..2b31d078c 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -1,6 +1,7 @@ import { render, TimeToFirstDraw, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid" import * as Clipboard from "@tui/util/clipboard" import * as Selection from "@tui/util/selection" +import * as Terminal from "@tui/util/terminal" import { createCliRenderer, MouseButton, type CliRendererConfig } from "@opentui/core" import { RouteProvider, useRoute } from "@tui/context/route" import { @@ -119,6 +120,12 @@ export function tui(input: { const unguard = win32InstallCtrlCGuard() win32DisableProcessedInput() + const mode = await Terminal.getTerminalBackgroundColor() + + // Re-clear after getTerminalBackgroundColor() because setRawMode(false) + // restores the original console mode, including processed input on Windows. + win32DisableProcessedInput() + const onExit = async () => { unguard?.() resolve() @@ -129,7 +136,6 @@ export function tui(input: { } const renderer = await createCliRenderer(rendererConfig(input.config)) - const mode = (await renderer.waitForThemeMode(1000)) ?? "dark" await render(() => { return ( diff --git a/packages/opencode/src/cli/cmd/tui/util/terminal.ts b/packages/opencode/src/cli/cmd/tui/util/terminal.ts index c026b7381..a61390f2c 100644 --- a/packages/opencode/src/cli/cmd/tui/util/terminal.ts +++ b/packages/opencode/src/cli/cmd/tui/util/terminal.ts @@ -17,6 +17,12 @@ function parse(color: string): RGBA | null { return null } +function mode(background: RGBA | null): "dark" | "light" { + if (!background) return "dark" + const luminance = (0.299 * background.r + 0.587 * background.g + 0.114 * background.b) / 255 + return luminance > 0.5 ? "light" : "dark" +} + /** * Query terminal colors including background, foreground, and palette (0-15). * Uses OSC escape sequences to retrieve actual terminal color values. @@ -94,3 +100,36 @@ export async function colors(): Promise<{ }, 1000) }) } + +// Keep startup mode detection separate from `colors()`: the TUI boot path only +// needs OSC 11 and should resolve on the first background response instead of +// waiting on the full palette query used by system theme generation. +export async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { + if (!process.stdin.isTTY) return "dark" + + return new Promise((resolve) => { + let timeout: NodeJS.Timeout + + const cleanup = () => { + process.stdin.setRawMode(false) + process.stdin.removeListener("data", handler) + clearTimeout(timeout) + } + + const handler = (data: Buffer) => { + const match = data.toString().match(/\x1b]11;([^\x07\x1b]+)/) + if (!match) return + cleanup() + resolve(mode(parse(match[1]))) + } + + process.stdin.setRawMode(true) + process.stdin.on("data", handler) + process.stdout.write("\x1b]11;?\x07") + + timeout = setTimeout(() => { + cleanup() + resolve("dark") + }, 1000) + }) +} diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 110d6a091..231d16e5e 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -22,8 +22,8 @@ "zod": "catalog:" }, "peerDependencies": { - "@opentui/core": ">=0.1.101", - "@opentui/solid": ">=0.1.101" + "@opentui/core": ">=0.1.99", + "@opentui/solid": ">=0.1.99" }, "peerDependenciesMeta": { "@opentui/core": { @@ -34,8 +34,8 @@ } }, "devDependencies": { - "@opentui/core": "0.1.101", - "@opentui/solid": "0.1.101", + "@opentui/core": "0.1.99", + "@opentui/solid": "0.1.99", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "typescript": "catalog:", From 3205f122eb7a1c97c63ee18f7069ea2248e2b2b4 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 21 Apr 2026 16:57:27 +0000 Subject: [PATCH 021/426] 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 e68adae5b..21279a327 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-DOGOZdPdkcuyDhVAyWHGsL4rrV28S+YFZj/VORuoQ8Q=", - "aarch64-linux": "sha256-WRnAaEoKvgFFZ+UkbYtD9gBw0HtV1jdUqv7yUE2uTAQ=", - "aarch64-darwin": "sha256-LxIj/dsL88M99T3WLaD9FL6Qdu2TV+kr1RMZaZ3i4WM=", - "x86_64-darwin": "sha256-PgIvplw6yz9KN5nBWox3BXZIXDbkJ3ZuDPKKSVF82MU=" + "x86_64-linux": "sha256-NczRp8MPppkqP8PQfWMUWJ/Wofvf2YVy5m4i22Pi3jg=", + "aarch64-linux": "sha256-QIxGOu8Fj+sWgc9hKvm1BLiIErxEtd17SPlwZGac9sQ=", + "aarch64-darwin": "sha256-Rb9qbMM+ARn0iBCaZurwcoUBCplbMXEZwrXVKextp3I=", + "x86_64-darwin": "sha256-KVxOKkaVV7W+K4reEk14MTLgmtoqwCYDqDNXNeS6ync=" } } From ecc06a3d8f7783d3759061c3404341b0cdc537ec Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 14:06:47 -0400 Subject: [PATCH 022/426] refactor(core): make Config.Info canonical Effect Schema (#23716) --- packages/opencode/script/schema.ts | 2 +- packages/opencode/src/config/config.ts | 35 ++++++++++++------- packages/opencode/src/server/routes/global.ts | 6 ++-- .../src/server/routes/instance/config.ts | 6 ++-- .../server/routes/instance/httpapi/config.ts | 2 +- packages/opencode/test/config/config.test.ts | 10 +++--- .../opencode/test/session/compaction.test.ts | 2 +- 7 files changed, 36 insertions(+), 27 deletions(-) diff --git a/packages/opencode/script/schema.ts b/packages/opencode/script/schema.ts index c0f302f21..448760ae1 100755 --- a/packages/opencode/script/schema.ts +++ b/packages/opencode/script/schema.ts @@ -55,7 +55,7 @@ const configFile = process.argv[2] const tuiFile = process.argv[3] console.log(configFile) -await Bun.write(configFile, JSON.stringify(generate(Config.Info), null, 2)) +await Bun.write(configFile, JSON.stringify(generate(Config.Info.zod), null, 2)) if (tuiFile) { console.log(tuiFile) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 7fe337176..b4f4ace67 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -25,6 +25,7 @@ import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "e import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" import { InstanceRef } from "@/effect/instance-ref" import { zod, ZodOverride } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import { ConfigAgent } from "./agent" import { ConfigCommand } from "./command" import { ConfigFormatter } from "./formatter" @@ -91,7 +92,15 @@ const LogLevelRef = Schema.Any.annotate({ [ZodOverride]: Log.Level }) const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) const NonNegativeInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) -export const InfoSchema = Schema.Struct({ +// The Effect Schema is the canonical source of truth. The `.zod` compatibility +// surface is derived so existing Hono validators keep working without a parallel +// Zod definition. +// +// The walker emits `z.object({...})` which is non-strict by default. Config +// historically uses `.strict()` (additionalProperties: false in openapi.json), +// so layer that on after derivation. Re-apply the Config ref afterward +// since `.strict()` strips the walker's meta annotation. +export const Info = Schema.Struct({ $schema: Schema.optional(Schema.String).annotate({ description: "JSON schema reference for configuration validation", }), @@ -235,6 +244,14 @@ export const InfoSchema = Schema.Struct({ }), ), }) + .annotate({ identifier: "Config" }) + .pipe( + withStatics((s) => ({ + zod: (zod(s) as unknown as z.ZodObject) + .strict() + .meta({ ref: "Config" }) as unknown as z.ZodType>>, + })), + ) // Schema.Struct produces readonly types by default, but the service code // below mutates Info objects directly (e.g. `config.mode = ...`). Strip the @@ -256,15 +273,7 @@ type DeepMutable = T extends readonly [unknown, ...unknown[]] ? { -readonly [K in keyof T]: DeepMutable } : T -// The walker emits `z.object({...})` which is non-strict by default. Config -// historically uses `.strict()` (additionalProperties: false in openapi.json), -// so layer that on after derivation. Re-apply the Config ref afterward -// since `.strict()` strips the walker's meta annotation. -export const Info = (zod(InfoSchema) as unknown as z.ZodObject) - .strict() - .meta({ ref: "Config" }) as unknown as z.ZodType>> - -export type Info = z.output & { +export type Info = DeepMutable> & { // plugin_origins is derived state, not a persisted config field. It keeps each winning plugin spec together // with the file and scope it came from so later runtime code can make location-sensitive decisions. plugin_origins?: ConfigPlugin.Origin[] @@ -361,7 +370,7 @@ export const layer = Layer.effect( ), ) const parsed = ConfigParse.jsonc(expanded, source) - const data = ConfigParse.schema(Info, normalizeLoadedConfig(parsed, source), source) + const data = ConfigParse.schema(Info.zod, normalizeLoadedConfig(parsed, source), source) if (!("path" in options)) return data yield* Effect.promise(() => resolveLoadedPlugins(data, options.path)) @@ -753,13 +762,13 @@ export const layer = Layer.effect( let next: Info if (!file.endsWith(".jsonc")) { - const existing = ConfigParse.schema(Info, ConfigParse.jsonc(before, file), file) + const existing = ConfigParse.schema(Info.zod, ConfigParse.jsonc(before, file), file) const merged = mergeDeep(writable(existing), writable(config)) yield* fs.writeFileString(file, JSON.stringify(merged, null, 2)).pipe(Effect.orDie) next = merged } else { const updated = patchJsonc(before, writable(config)) - next = ConfigParse.schema(Info, ConfigParse.jsonc(updated, file), file) + next = ConfigParse.schema(Info.zod, ConfigParse.jsonc(updated, file), file) yield* fs.writeFileString(file, updated).pipe(Effect.orDie) } diff --git a/packages/opencode/src/server/routes/global.ts b/packages/opencode/src/server/routes/global.ts index 8208cf966..54f9972e0 100644 --- a/packages/opencode/src/server/routes/global.ts +++ b/packages/opencode/src/server/routes/global.ts @@ -147,7 +147,7 @@ export const GlobalRoutes = lazy(() => description: "Get global config info", content: { "application/json": { - schema: resolver(Config.Info), + schema: resolver(Config.Info.zod), }, }, }, @@ -168,14 +168,14 @@ export const GlobalRoutes = lazy(() => description: "Successfully updated global config", content: { "application/json": { - schema: resolver(Config.Info), + schema: resolver(Config.Info.zod), }, }, }, ...errors(400), }, }), - validator("json", Config.Info), + validator("json", Config.Info.zod), async (c) => { const config = c.req.valid("json") const next = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.updateGlobal(config))) diff --git a/packages/opencode/src/server/routes/instance/config.ts b/packages/opencode/src/server/routes/instance/config.ts index 7f368cd31..88e5feef9 100644 --- a/packages/opencode/src/server/routes/instance/config.ts +++ b/packages/opencode/src/server/routes/instance/config.ts @@ -20,7 +20,7 @@ export const ConfigRoutes = lazy(() => description: "Get config info", content: { "application/json": { - schema: resolver(Config.Info), + schema: resolver(Config.Info.zod), }, }, }, @@ -43,14 +43,14 @@ export const ConfigRoutes = lazy(() => description: "Successfully updated config", content: { "application/json": { - schema: resolver(Config.Info), + schema: resolver(Config.Info.zod), }, }, }, ...errors(400), }, }), - validator("json", Config.Info), + validator("json", Config.Info.zod), async (c) => jsonRequest("ConfigRoutes.update", c, function* () { const config = c.req.valid("json") diff --git a/packages/opencode/src/server/routes/instance/httpapi/config.ts b/packages/opencode/src/server/routes/instance/httpapi/config.ts index 678e96e33..2dfdec172 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/config.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/config.ts @@ -10,7 +10,7 @@ export const ConfigApi = HttpApi.make("config") HttpApiGroup.make("config") .add( HttpApiEndpoint.get("get", root, { - success: Config.InfoSchema, + success: Config.Info, }).annotateMerge( OpenApi.annotations({ identifier: "config.get", diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 3fafdadaa..e9b053819 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -2221,7 +2221,7 @@ describe("OPENCODE_CONFIG_CONTENT token substitution", () => { test("parseManagedPlist strips MDM metadata keys", async () => { const config = ConfigParse.schema( - Config.Info, + Config.Info.zod, ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ @@ -2249,7 +2249,7 @@ test("parseManagedPlist strips MDM metadata keys", async () => { test("parseManagedPlist parses server settings", async () => { const config = ConfigParse.schema( - Config.Info, + Config.Info.zod, ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ @@ -2269,7 +2269,7 @@ test("parseManagedPlist parses server settings", async () => { test("parseManagedPlist parses permission rules", async () => { const config = ConfigParse.schema( - Config.Info, + Config.Info.zod, ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ @@ -2299,7 +2299,7 @@ test("parseManagedPlist parses permission rules", async () => { test("parseManagedPlist parses enabled_providers", async () => { const config = ConfigParse.schema( - Config.Info, + Config.Info.zod, ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ @@ -2316,7 +2316,7 @@ test("parseManagedPlist parses enabled_providers", async () => { test("parseManagedPlist handles empty config", async () => { const config = ConfigParse.schema( - Config.Info, + Config.Info.zod, ConfigParse.jsonc( await ConfigManaged.parseManagedPlist(JSON.stringify({ $schema: "https://opencode.ai/config.json" })), "test:mobileconfig", diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 14b47922b..0e2b179f0 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -168,7 +168,7 @@ function layer(result: "continue" | "compact") { } function cfg(compaction?: Config.Info["compaction"]) { - const base = Config.Info.parse({}) + const base = Config.Info.zod.parse({}) return Layer.mock(Config.Service)({ get: () => Effect.succeed({ ...base, compaction }), }) From 1e1a500603de7891f2f71dcd79b56678288fce20 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 21 Apr 2026 18:08:04 +0000 Subject: [PATCH 023/426] chore: generate --- packages/opencode/src/config/config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index b4f4ace67..4af079127 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -247,9 +247,9 @@ export const Info = Schema.Struct({ .annotate({ identifier: "Config" }) .pipe( withStatics((s) => ({ - zod: (zod(s) as unknown as z.ZodObject) - .strict() - .meta({ ref: "Config" }) as unknown as z.ZodType>>, + zod: (zod(s) as unknown as z.ZodObject).strict().meta({ ref: "Config" }) as unknown as z.ZodType< + DeepMutable> + >, })), ) From c9fb8d0ce70b632d262bcb594793e5496d16b61a Mon Sep 17 00:00:00 2001 From: opencode Date: Tue, 21 Apr 2026 19:07:36 +0000 Subject: [PATCH 024/426] sync release versions for v1.14.20 --- bun.lock | 32 +++++++++++++------------- 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-electron/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/shared/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index ff186b675..77ab24240 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -83,7 +83,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -117,7 +117,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -144,7 +144,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -168,7 +168,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -192,7 +192,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -225,7 +225,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -269,7 +269,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -298,7 +298,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -314,7 +314,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.14.19", + "version": "1.14.20", "bin": { "opencode": "./bin/opencode", }, @@ -459,7 +459,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -494,7 +494,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "cross-spawn": "catalog:", }, @@ -509,7 +509,7 @@ }, "packages/shared": { "name": "@opencode-ai/shared", - "version": "1.14.19", + "version": "1.14.20", "bin": { "opencode": "./bin/opencode", }, @@ -533,7 +533,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -568,7 +568,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -617,7 +617,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.14.19", + "version": "1.14.20", "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 73a648cb6..f461459fc 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.14.19", + "version": "1.14.20", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 6f63db152..d8c9434bf 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.14.19", + "version": "1.14.20", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 3605cfb0e..090e5c59d 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.14.19", + "version": "1.14.20", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index da73bc61f..a2e0c5f03 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.14.19", + "version": "1.14.20", "$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 b66296670..7aa0cb757 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.14.19", + "version": "1.14.20", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 7105cb50e..155e43f5e 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.14.19", + "version": "1.14.20", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 7b60658f4..a3b6eac6b 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.14.19", + "version": "1.14.20", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 4783381f1..016863650 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.14.19", + "version": "1.14.20", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index cf48deae1..cee69b11a 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.14.19" +version = "1.14.20" 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.14.19/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.20/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.19/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.20/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.19/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.20/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.14.19/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.20/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.14.19/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.20/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index f01d607e3..4e920b688 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.14.19", + "version": "1.14.20", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 199f4b215..dcd0a9ad6 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.19", + "version": "1.14.20", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 231d16e5e..64d12eb42 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.14.19", + "version": "1.14.20", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 6769ba391..a6ba6ee9f 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.14.19", + "version": "1.14.20", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/shared/package.json b/packages/shared/package.json index cb2b04ee5..22a8c9c07 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.19", + "version": "1.14.20", "name": "@opencode-ai/shared", "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index 67dd7ef35..7a016907a 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.14.19", + "version": "1.14.20", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 9dccd909a..dcf57345e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.14.19", + "version": "1.14.20", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index d29cc6fc5..8fd33564d 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.14.19", + "version": "1.14.20", "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 bef204987..720c70196 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.14.19", + "version": "1.14.20", "publisher": "sst-dev", "repository": { "type": "git", From cd6415f332f53993d25f5371801cc46a123c6ef3 Mon Sep 17 00:00:00 2001 From: Rahul Iyer <4255590+rahuliyer95@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:30:15 -0400 Subject: [PATCH 025/426] fix(tui): don't check for version upgrades if it's disabled by the user (#20089) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/cli/upgrade.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/upgrade.ts b/packages/opencode/src/cli/upgrade.ts index 7c6f08874..a3e3f3013 100644 --- a/packages/opencode/src/cli/upgrade.ts +++ b/packages/opencode/src/cli/upgrade.ts @@ -7,6 +7,7 @@ import { InstallationVersion } from "@/installation/version" export async function upgrade() { const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.getGlobal())) + if (config.autoupdate === false || Flag.OPENCODE_DISABLE_AUTOUPDATE) return const method = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.method())) const latest = await AppRuntime.runPromise(Installation.Service.use((svc) => svc.latest(method))).catch(() => {}) if (!latest) return @@ -17,7 +18,6 @@ export async function upgrade() { } if (InstallationVersion === latest) return - if (config.autoupdate === false || Flag.OPENCODE_DISABLE_AUTOUPDATE) return const kind = Installation.getReleaseType(InstallationVersion, latest) From 58232d896eb9f358780e224a2bf1b6540fd8bb5a Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:33:35 -0400 Subject: [PATCH 026/426] fix: dont show variants for kimi models that dont support them (#23696) --- packages/opencode/src/provider/transform.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 4ed43ce99..863f01273 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -410,7 +410,7 @@ export function variants(model: Provider.Model): Record Date: Tue, 21 Apr 2026 20:17:13 +0000 Subject: [PATCH 027/426] Update VOUCHED list https://github.com/anomalyco/opencode/issues/23735#issuecomment-4291498854 --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index 8bc1d8e2b..65bda9804 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -27,6 +27,7 @@ r44vc0rp rekram1-node -ricardo-m-l -robinmordasiewicz +rubdos shantur simonklee -spider-yamet clawdbot/llm psychosis, spam pinging the team From 1a20703469e46e4cc3682843d549b0f3235946d1 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Tue, 21 Apr 2026 22:45:06 +0200 Subject: [PATCH 028/426] feat: add Mistral Small reasoning variant support (issue #19479) (#23735) --- packages/opencode/src/provider/transform.ts | 10 ++++-- .../opencode/test/provider/transform.test.ts | 35 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 863f01273..1d84c7c93 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -408,7 +408,6 @@ export function variants(model: Provider.Model): Record { expect(result).toEqual({}) }) - test("mistral returns empty object", () => { + test("mistral with reasoning returns variants", () => { + const model = createMockModel({ + id: "mistral/mistral-small-latest", + providerID: "mistral", + api: { + id: "mistral-small-latest", + url: "https://api.mistral.com", + npm: "@ai-sdk/mistral", + }, + capabilities: { reasoning: true }, + }) + const result = ProviderTransform.variants(model) + expect(result).toEqual({ + high: { reasoningEffort: "high" }, + }) + }) + + test("mistral without reasoning returns empty object", () => { const model = createMockModel({ id: "mistral/mistral-large", providerID: "mistral", @@ -2122,6 +2139,22 @@ describe("ProviderTransform.variants", () => { url: "https://api.mistral.com", npm: "@ai-sdk/mistral", }, + capabilities: { reasoning: false }, + }) + const result = ProviderTransform.variants(model) + expect(result).toEqual({}) + }) + + test("mistral large with reasoning returns empty object (only small supports reasoning)", () => { + const model = createMockModel({ + id: "mistral/mistral-large", + providerID: "mistral", + api: { + id: "mistral-large-latest", + url: "https://api.mistral.com", + npm: "@ai-sdk/mistral", + }, + capabilities: { reasoning: true }, }) const result = ProviderTransform.variants(model) expect(result).toEqual({}) From caaddf096424193ef3cba4bf2a8f9ae6876915bc Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 21 Apr 2026 17:06:29 -0400 Subject: [PATCH 029/426] zen: ling 2.6 free --- packages/web/src/content/docs/ar/zen.mdx | 8 ++++++-- packages/web/src/content/docs/bs/zen.mdx | 10 +++++++--- packages/web/src/content/docs/da/zen.mdx | 10 +++++++--- packages/web/src/content/docs/de/zen.mdx | 8 ++++++-- packages/web/src/content/docs/es/zen.mdx | 10 +++++++--- packages/web/src/content/docs/fr/zen.mdx | 8 ++++++-- packages/web/src/content/docs/it/zen.mdx | 10 +++++++--- packages/web/src/content/docs/ja/zen.mdx | 8 ++++++-- packages/web/src/content/docs/ko/zen.mdx | 8 ++++++-- packages/web/src/content/docs/nb/zen.mdx | 10 +++++++--- packages/web/src/content/docs/pl/zen.mdx | 10 +++++++--- packages/web/src/content/docs/pt-br/zen.mdx | 8 ++++++-- packages/web/src/content/docs/ru/zen.mdx | 10 +++++++--- packages/web/src/content/docs/th/zen.mdx | 8 ++++++-- packages/web/src/content/docs/tr/zen.mdx | 8 ++++++-- packages/web/src/content/docs/zen.mdx | 10 +++++++--- packages/web/src/content/docs/zh-cn/zen.mdx | 8 ++++++-- packages/web/src/content/docs/zh-tw/zen.mdx | 8 ++++++-- 18 files changed, 116 insertions(+), 44 deletions(-) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index 6150c7295..c30cf3287 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -95,9 +95,10 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -يستخدم [معرّف النموذج](/docs/config/#models) في إعدادات OpenCode الصيغة `opencode/`. على سبيل المثال، بالنسبة إلى GPT 5.3 Codex، ستستخدم `opencode/gpt-5.3-codex` في إعداداتك. +يستخدم [معرّف النموذج](/docs/config/#models) في إعدادات OpenCode الصيغة `opencode/`. على سبيل المثال، بالنسبة إلى GPT 5.4، ستستخدم `opencode/gpt-5.4` في إعداداتك. --- @@ -118,8 +119,9 @@ https://opencode.ai/zen/v1/models | النموذج | الإدخال | الإخراج | القراءة المخزنة | الكتابة المخزنة | | --------------------------------- | ------- | ------- | --------------- | --------------- | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -167,6 +169,7 @@ https://opencode.ai/zen/v1/models النماذج المجانية: - MiniMax M2.5 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. +- Ling 2.6 Flash Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Nemotron 3 Super Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Big Pickle نموذج خفي ومتاح مجانا على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. @@ -210,6 +213,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - MiniMax M2.5 Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. +- Ling 2.6 Flash Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - Nemotron 3 Super Free (نقاط نهاية NVIDIA المجانية): يُقدَّم بموجب [شروط خدمة النسخة التجريبية من واجهة NVIDIA API](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). للاستخدام التجريبي فقط، وليس للإنتاج أو البيانات الحساسة. تقوم NVIDIA بتسجيل المطالبات والمخرجات لتحسين نماذجها وخدماتها. لا ترسل بيانات شخصية أو سرية. - OpenAI APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index 70ac7641f..b6fc90fa0 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -100,11 +100,12 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) u vašoj OpenCode konfiguraciji koristi format -`opencode/`. Na primjer, za GPT 5.3 Codex u konfiguraciji biste -koristili `opencode/gpt-5.3-codex`. +`opencode/`. Na primjer, za GPT 5.4 u konfiguraciji biste +koristili `opencode/gpt-5.4`. --- @@ -125,8 +126,9 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -174,6 +176,7 @@ Naknade za kreditne kartice prosljeđujemo po stvarnom trošku (4.4% + $0.30 po Besplatni modeli: - MiniMax M2.5 Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. +- Ling 2.6 Flash Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Nemotron 3 Super Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Big Pickle je stealth model koji je besplatan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. @@ -222,6 +225,7 @@ i ne koriste vaše podatke za treniranje modela, uz sljedeće izuzetke: - Big Pickle: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - MiniMax M2.5 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. +- Ling 2.6 Flash Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - Nemotron 3 Super Free (besplatni NVIDIA endpointi): Dostupan je prema [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Samo za probnu upotrebu, nije za produkciju niti osjetljive podatke. NVIDIA bilježi promptove i izlaze radi poboljšanja svojih modela i usluga. Nemojte slati lične ili povjerljive podatke. - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index c497f35b7..ea5189ec2 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -100,11 +100,12 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) i din OpenCode-konfiguration -bruger formatet `opencode/`. For eksempel ville du for GPT 5.3 Codex -bruge `opencode/gpt-5.3-codex` i din konfiguration. +bruger formatet `opencode/`. For eksempel ville du for GPT 5.4 +bruge `opencode/gpt-5.4` i din konfiguration. --- @@ -125,8 +126,9 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -174,6 +176,7 @@ Kreditkortgebyrer videregives til kostpris (4.4% + $0.30 pr. transaktion); vi op De gratis modeller: - MiniMax M2.5 Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. +- Ling 2.6 Flash Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Nemotron 3 Super Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Big Pickle er en stealth-model, som er gratis på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. @@ -220,6 +223,7 @@ Alle vores modeller hostes i US. Vores udbydere følger en nul-opbevaringspoliti - Big Pickle: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - MiniMax M2.5 Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. +- Ling 2.6 Flash Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - Nemotron 3 Super Free (gratis NVIDIA-endpoints): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun til prøvebrug, ikke til produktion eller følsomme data. Prompts og outputs logges af NVIDIA for at forbedre deres modeller og tjenester. Indsend ikke personlige eller fortrolige data. - OpenAI APIs: Anmodninger opbevares i 30 dage i overensstemmelse med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Anmodninger opbevares i 30 dage i overensstemmelse med [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index 0dfc72850..b8a09497c 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -91,9 +91,10 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -Die [Model-ID](/docs/config/#models) in deiner OpenCode-Konfiguration verwendet das Format `opencode/`. Für GPT 5.3 Codex würdest du zum Beispiel `opencode/gpt-5.3-codex` in deiner Konfiguration verwenden. +Die [Model-ID](/docs/config/#models) in deiner OpenCode-Konfiguration verwendet das Format `opencode/`. Für GPT 5.4 würdest du zum Beispiel `opencode/gpt-5.4` in deiner Konfiguration verwenden. --- @@ -114,8 +115,9 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -163,6 +165,7 @@ Kreditkartengebühren werden zum Selbstkostenpreis weitergegeben (4.4% + $0.30 p Die kostenlosen Modelle: - MiniMax M2.5 Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. +- Ling 2.6 Flash Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Nemotron 3 Super Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Big Pickle ist ein Stealth-Modell, das für begrenzte Zeit kostenlos auf OpenCode verfügbar ist. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. @@ -206,6 +209,7 @@ Alle unsere Modelle werden in den USA gehostet. Unsere Provider folgen einer Zer - Big Pickle: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - MiniMax M2.5 Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. +- Ling 2.6 Flash Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - Nemotron 3 Super Free (kostenlose NVIDIA-Endpunkte): Bereitgestellt gemäß den [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Nur für Testzwecke, nicht für Produktion oder sensible Daten. Eingaben und Ausgaben werden von NVIDIA protokolliert, um seine Modelle und Dienste zu verbessern. Übermitteln Sie keine personenbezogenen oder vertraulichen Daten. - OpenAI APIs: Anfragen werden in Übereinstimmung mit [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 30 Tage lang gespeichert. - Anthropic APIs: Anfragen werden in Übereinstimmung mit [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 30 Tage lang gespeichert. diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 4a2866aa7..b8999a050 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -100,11 +100,12 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | El [identificador del modelo](/docs/config/#models) en tu configuración de OpenCode -usa el formato `opencode/`. Por ejemplo, para GPT 5.3 Codex, usarías -`opencode/gpt-5.3-codex` en tu configuración. +usa el formato `opencode/`. Por ejemplo, para GPT 5.4, usarías +`opencode/gpt-5.4` en tu configuración. --- @@ -125,8 +126,9 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Modelo | Entrada | Salida | Lectura en caché | Escritura en caché | | --------------------------------- | ------- | ------- | ---------------- | ------------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -174,6 +176,7 @@ Las comisiones de tarjeta de crédito se trasladan al costo (4.4% + $0.30 por tr Los modelos gratuitos: - MiniMax M2.5 Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. +- Ling 2.6 Flash Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Nemotron 3 Super Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Big Pickle es un modelo stealth que es gratuito en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. @@ -220,6 +223,7 @@ Todos nuestros modelos están alojados en US. Nuestros proveedores siguen una po - Big Pickle: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - MiniMax M2.5 Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. +- Ling 2.6 Flash Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - Nemotron 3 Super Free (endpoints gratuitos de NVIDIA): Se ofrece bajo los [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo para uso de prueba, no para producción ni datos sensibles. NVIDIA registra los prompts y las salidas para mejorar sus modelos y servicios. No envíes datos personales ni confidenciales. - OpenAI APIs: Las solicitudes se conservan durante 30 días de acuerdo con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Las solicitudes se conservan durante 30 días de acuerdo con [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index 247ae00d2..ab759f053 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -91,9 +91,10 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -Le [model id](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode/`. Par exemple, pour GPT 5.3 Codex, vous utiliseriez `opencode/gpt-5.3-codex` dans votre configuration. +Le [model id](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode/`. Par exemple, pour GPT 5.4, vous utiliseriez `opencode/gpt-5.4` dans votre configuration. --- @@ -114,8 +115,9 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Modèle | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -163,6 +165,7 @@ Les frais de carte de crédit sont répercutés au prix coûtant (4.4% + $0.30 p Les modèles gratuits : - MiniMax M2.5 Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. +- Ling 2.6 Flash Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Nemotron 3 Super Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Big Pickle est un modèle stealth gratuit sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. @@ -206,6 +209,7 @@ Tous nos modèles sont hébergés aux US. Nos fournisseurs suivent une politique - Big Pickle : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - MiniMax M2.5 Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. +- Ling 2.6 Flash Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - Nemotron 3 Super Free (endpoints NVIDIA gratuits) : Fourni dans le cadre des [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Réservé à un usage d'essai, pas à la production ni aux données sensibles. Les prompts et les sorties sont journalisés par NVIDIA pour améliorer ses modèles et services. N'envoyez pas de données personnelles ou confidentielles. - OpenAI APIs : Les requêtes sont conservées pendant 30 jours conformément à [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs : Les requêtes sont conservées pendant 30 jours conformément à [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 0922c51a5..38c69b021 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -100,11 +100,12 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | Il [model id](/docs/config/#models) nella config di OpenCode -usa il formato `opencode/`. Per esempio, per GPT 5.3 Codex, useresti -`opencode/gpt-5.3-codex` nella tua config. +usa il formato `opencode/`. Per esempio, per GPT 5.4, useresti +`opencode/gpt-5.4` nella tua config. --- @@ -125,8 +126,9 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Modello | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -174,6 +176,7 @@ Le commissioni della carta di credito vengono trasferite al costo (4.4% + $0.30 I modelli gratuiti: - MiniMax M2.5 Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. +- Ling 2.6 Flash Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Nemotron 3 Super Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Big Pickle è un modello stealth che è gratuito su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. @@ -220,6 +223,7 @@ Tutti i nostri modelli sono ospitati negli US. I nostri provider seguono una pol - Big Pickle: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - MiniMax M2.5 Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. +- Ling 2.6 Flash Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - Nemotron 3 Super Free (endpoint NVIDIA gratuiti): fornito secondo i [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo per uso di prova, non per produzione o dati sensibili. NVIDIA registra prompt e output per migliorare i propri modelli e servizi. Non inviare dati personali o riservati. - OpenAI APIs: le richieste vengono conservate per 30 giorni in conformità con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: le richieste vengono conservate per 30 giorni in conformità con [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index 7419bd4c4..8f95473d6 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -91,9 +91,10 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -OpenCode 設定で使う [model id](/docs/config/#models) は `opencode/` 形式です。たとえば、GPT 5.3 Codex では設定に `opencode/gpt-5.3-codex` を使用します。 +OpenCode 設定で使う [model id](/docs/config/#models) は `opencode/` 形式です。たとえば、GPT 5.4 では設定に `opencode/gpt-5.4` を使用します。 --- @@ -114,8 +115,9 @@ https://opencode.ai/zen/v1/models | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -163,6 +165,7 @@ https://opencode.ai/zen/v1/models 無料モデル: - MiniMax M2.5 Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 +- Ling 2.6 Flash Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Nemotron 3 Super Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Big Pickle はステルスモデルで、期間限定で OpenCode で無料提供されています。チームはこの期間中にフィードバックを集め、モデルを改善しています。 @@ -206,6 +209,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - MiniMax M2.5 Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 +- Ling 2.6 Flash Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - Nemotron 3 Super Free(NVIDIA の無料エンドポイント): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) に基づいて提供されます。試用専用であり、本番環境や機密性の高いデータには使用しないでください。プロンプトと出力は、NVIDIA が自社のモデルとサービスを改善するために記録します。個人情報や機密データは送信しないでください。 - OpenAI APIs: リクエストは [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) に従って 30 日間保持されます。 - Anthropic APIs: リクエストは [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) に従って 30 日間保持されます。 diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 3d796ee99..59d744806 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -91,9 +91,10 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -OpenCode config에서 사용하는 [모델 ID](/docs/config/#models)는 `opencode/` 형식입니다. 예를 들어 GPT 5.3 Codex를 사용하려면 config에서 `opencode/gpt-5.3-codex`를 사용하면 됩니다. +OpenCode config에서 사용하는 [모델 ID](/docs/config/#models)는 `opencode/` 형식입니다. 예를 들어 GPT 5.4를 사용하려면 config에서 `opencode/gpt-5.4`를 사용하면 됩니다. --- @@ -114,8 +115,9 @@ https://opencode.ai/zen/v1/models | 모델 | 입력 | 출력 | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -163,6 +165,7 @@ https://opencode.ai/zen/v1/models 무료 모델: - MiniMax M2.5 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. +- Ling 2.6 Flash Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Nemotron 3 Super Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Big Pickle은 한정된 기간 동안 OpenCode에서 무료로 제공되는 stealth model입니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. @@ -206,6 +209,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - MiniMax M2.5 Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. +- Ling 2.6 Flash Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - Nemotron 3 Super Free(NVIDIA 무료 엔드포인트): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)에 따라 제공됩니다. 평가판 전용이며 프로덕션 환경이나 민감한 데이터에는 사용할 수 없습니다. NVIDIA는 자사 모델과 서비스를 개선하기 위해 프롬프트와 출력을 기록합니다. 개인 정보나 기밀 데이터는 제출하지 마세요. - OpenAI APIs: 요청은 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data)에 따라 30일 동안 보관됩니다. - Anthropic APIs: 요청은 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage)에 따라 30일 동안 보관됩니다. diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index 139d13da8..15c942732 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -100,11 +100,12 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [modell-id](/docs/config/#models) i OpenCode-konfigurasjonen din -bruker formatet `opencode/`. For eksempel, for GPT 5.3 Codex, ville du -brukt `opencode/gpt-5.3-codex` i konfigurasjonen din. +bruker formatet `opencode/`. For eksempel, for GPT 5.4, ville du +brukt `opencode/gpt-5.4` i konfigurasjonen din. --- @@ -125,8 +126,9 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Modell | Inndata | Utdata | Bufret lesing | Bufret skriving | | --------------------------------- | ------- | ------- | ------------- | --------------- | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -174,6 +176,7 @@ Kredittkortgebyrer videreføres til kostpris (4.4% + $0.30 per transaction); vi Gratis-modellene: - MiniMax M2.5 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. +- Ling 2.6 Flash Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Nemotron 3 Super Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Big Pickle er en stealth-modell som er gratis på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. @@ -220,6 +223,7 @@ Alle modellene våre hostes i US. Leverandørene våre følger en policy for zer - Big Pickle: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - MiniMax M2.5 Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. +- Ling 2.6 Flash Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - Nemotron 3 Super Free (gratis NVIDIA-endepunkter): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun for prøvebruk, ikke for produksjon eller sensitive data. Prompter og svar logges av NVIDIA for å forbedre modellene og tjenestene deres. Ikke send inn personopplysninger eller konfidensielle data. - OpenAI APIs: Forespørsler lagres i 30 dager i samsvar med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Forespørsler lagres i 30 dager i samsvar med [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 42a9bb3d1..483a3cedf 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -100,11 +100,12 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [ID modelu](/docs/config/#models) w Twojej konfiguracji OpenCode używa formatu -`opencode/`. Na przykład dla GPT 5.3 Codex użyjesz w konfiguracji -`opencode/gpt-5.3-codex`. +`opencode/`. Na przykład dla GPT 5.4 użyjesz w konfiguracji +`opencode/gpt-5.4`. --- @@ -125,8 +126,9 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Model | Wejście | Wyjście | Odczyt z cache | Zapis do cache | | --------------------------------- | ------- | ------- | -------------- | -------------- | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -175,6 +177,7 @@ Opłaty za karty kredytowe są przenoszone po kosztach (4.4% + $0.30 per transac Darmowe modele: - MiniMax M2.5 Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. +- Ling 2.6 Flash Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Nemotron 3 Super Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Big Pickle to stealth model, który jest darmowy w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. @@ -221,6 +224,7 @@ Wszystkie nasze modele są hostowane w US. Nasi dostawcy stosują politykę zero - Big Pickle: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - MiniMax M2.5 Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. +- Ling 2.6 Flash Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - Nemotron 3 Super Free (darmowe endpointy NVIDIA): Udostępniany zgodnie z [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Tylko do użytku próbnego, nie do produkcji ani danych wrażliwych. NVIDIA rejestruje prompty i odpowiedzi, aby ulepszać swoje modele i usługi. Nie przesyłaj danych osobowych ani poufnych. - OpenAI APIs: Żądania są przechowywane przez 30 dni zgodnie z [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Żądania są przechowywane przez 30 dni zgodnie z [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index a2bb269ce..1112066e2 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -91,9 +91,10 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -O [model id](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode/`. Por exemplo, para GPT 5.3 Codex, você usaria `opencode/gpt-5.3-codex` na sua configuração. +O [model id](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode/`. Por exemplo, para GPT 5.4, você usaria `opencode/gpt-5.4` na sua configuração. --- @@ -114,8 +115,9 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Modelo | Entrada | Saída | Leitura em cache | Escrita em cache | | --------------------------------- | ------- | ------- | ---------------- | ---------------- | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -163,6 +165,7 @@ As taxas de cartão de crédito são repassadas a preço de custo (4.4% + $0.30 Os modelos gratuitos: - MiniMax M2.5 Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. +- Ling 2.6 Flash Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Nemotron 3 Super Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Big Pickle é um modelo stealth que está gratuito no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. @@ -206,6 +209,7 @@ Todos os nossos modelos são hospedados nos US. Nossos provedores seguem uma pol - Big Pickle: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - MiniMax M2.5 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. +- Ling 2.6 Flash Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - Nemotron 3 Super Free (endpoints gratuitos da NVIDIA): Fornecido sob os [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Apenas para uso de avaliação, não para produção nem dados sensíveis. A NVIDIA registra prompts e saídas para melhorar seus modelos e serviços. Não envie dados pessoais ou confidenciais. - OpenAI APIs: As solicitações são retidas por 30 dias de acordo com [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: As solicitações são retidas por 30 dias de acordo com [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 8d1b11a10..27d81354e 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -100,11 +100,12 @@ OpenCode Zen работает как любой другой провайдер | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [идентификатор модели](/docs/config/#models) в вашей конфигурации OpenCode -использует формат `opencode/`. Например, для GPT 5.3 Codex вам нужно -использовать `opencode/gpt-5.3-codex` в своей конфигурации. +использует формат `opencode/`. Например, для GPT 5.4 вам нужно +использовать `opencode/gpt-5.4` в своей конфигурации. --- @@ -125,8 +126,9 @@ https://opencode.ai/zen/v1/models | Модель | Вход | Выход | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -174,6 +176,7 @@ https://opencode.ai/zen/v1/models Бесплатные модели: - MiniMax M2.5 Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. +- Ling 2.6 Flash Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Nemotron 3 Super Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Big Pickle — это скрытая модель, которая доступна бесплатно в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. @@ -220,6 +223,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - MiniMax M2.5 Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. +- Ling 2.6 Flash Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - Nemotron 3 Super Free (бесплатные эндпоинты NVIDIA): предоставляется в соответствии с [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Только для пробного использования, не для продакшена и не для чувствительных данных. NVIDIA логирует запросы и ответы, чтобы улучшать свои модели и сервисы. Не отправляйте персональные или конфиденциальные данные. - OpenAI APIs: запросы хранятся 30 дней в соответствии с [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: запросы хранятся 30 дней в соответствии с [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index c3b298a32..78de61a9b 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -93,9 +93,10 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -[model id](/docs/config/#models) ใน OpenCode config ของคุณใช้รูปแบบ `opencode/` ตัวอย่างเช่น สำหรับ GPT 5.3 Codex คุณจะใช้ `opencode/gpt-5.3-codex` ใน config ของคุณ +[model id](/docs/config/#models) ใน OpenCode config ของคุณใช้รูปแบบ `opencode/` ตัวอย่างเช่น สำหรับ GPT 5.4 คุณจะใช้ `opencode/gpt-5.4` ใน config ของคุณ --- @@ -116,8 +117,9 @@ https://opencode.ai/zen/v1/models | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -165,6 +167,7 @@ https://opencode.ai/zen/v1/models โมเดลฟรี: - MiniMax M2.5 Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล +- Ling 2.6 Flash Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Nemotron 3 Super Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Big Pickle เป็น stealth model ที่ใช้งานฟรีบน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล @@ -208,6 +211,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - MiniMax M2.5 Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล +- Ling 2.6 Flash Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - Nemotron 3 Super Free (endpoint ฟรีของ NVIDIA): ให้บริการภายใต้ [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) ใช้สำหรับการทดลองเท่านั้น ไม่เหมาะสำหรับ production หรือข้อมูลที่อ่อนไหว NVIDIA จะบันทึก prompt และ output เพื่อนำไปปรับปรุงโมเดลและบริการของตน โปรดอย่าส่งข้อมูลส่วนบุคคลหรือข้อมูลลับ. - OpenAI APIs: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index 754029305..409c11a21 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -91,9 +91,10 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -OpenCode yapılandırmanızdaki [model id](/docs/config/#models) `opencode/` biçimini kullanır. Örneğin, GPT 5.3 Codex için yapılandırmanızda `opencode/gpt-5.3-codex` kullanırsınız. +OpenCode yapılandırmanızdaki [model id](/docs/config/#models) `opencode/` biçimini kullanır. Örneğin, GPT 5.4 için yapılandırmanızda `opencode/gpt-5.4` kullanırsınız. --- @@ -114,8 +115,9 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -163,6 +165,7 @@ Kredi kartı ücretleri maliyet üzerinden yansıtılır (%4.4 + işlem başına Ücretsiz modeller: - MiniMax M2.5 Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. +- Ling 2.6 Flash Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Nemotron 3 Super Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Big Pickle, sınırlı bir süre için OpenCode'da ücretsiz olan gizli bir modeldir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. @@ -206,6 +209,7 @@ Tüm modellerimiz US'de barındırılıyor. Sağlayıcılarımız zero-retention - Big Pickle: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - MiniMax M2.5 Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. +- Ling 2.6 Flash Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - Nemotron 3 Super Free (ücretsiz NVIDIA uç noktaları): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) kapsamında sunulur. Yalnızca deneme amaçlıdır; üretim veya hassas veriler için uygun değildir. NVIDIA, modellerini ve hizmetlerini geliştirmek için promptları ve çıktıları kaydeder. Kişisel veya gizli veri göndermeyin. - OpenAI APIs: İstekler [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) uyarınca 30 gün boyunca saklanır. - Anthropic APIs: İstekler [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) uyarınca 30 gün boyunca saklanır. diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index ec44a0548..1c59c00f7 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -100,11 +100,12 @@ You can also access our models through the following API endpoints. | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | The [model id](/docs/config/#models) in your OpenCode config -uses the format `opencode/`. For example, for GPT 5.3 Codex, you would -use `opencode/gpt-5.3-codex` in your config. +uses the format `opencode/`. For example, for GPT 5.4, you would +use `opencode/gpt-5.4` in your config. --- @@ -125,8 +126,9 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Model | Input | Output | Cached Read | Cached Write | | --------------------------------- | ------ | ------- | ----------- | ------------ | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -174,6 +176,7 @@ Credit card fees are passed along at cost (4.4% + $0.30 per transaction); we don The free models: - MiniMax M2.5 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. +- Ling 2.6 Flash Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Nemotron 3 Super Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. @@ -220,6 +223,7 @@ All our models are hosted in the US. Our providers follow a zero-retention polic - Big Pickle: During its free period, collected data may be used to improve the model. - MiniMax M2.5 Free: During its free period, collected data may be used to improve the model. +- Ling 2.6 Flash Free: During its free period, collected data may be used to improve the model. - Nemotron 3 Super Free (NVIDIA free endpoints): Provided under the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Trial use only — not for production or sensitive data. Prompts and outputs are logged by NVIDIA to improve its models and services. Do not submit personal or confidential data. - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index 8eedcf31c..017c5b6c9 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -91,9 +91,10 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -在你的 OpenCode 配置中,[模型 ID](/docs/config/#models) 使用 `opencode/` 格式。例如,对于 GPT 5.3 Codex,你需要在配置中使用 `opencode/gpt-5.3-codex`。 +在你的 OpenCode 配置中,[模型 ID](/docs/config/#models) 使用 `opencode/` 格式。例如,对于 GPT 5.4,你需要在配置中使用 `opencode/gpt-5.4`。 --- @@ -114,8 +115,9 @@ https://opencode.ai/zen/v1/models | 模型 | 输入 | 输出 | 缓存读取 | 缓存写入 | | --------------------------------- | ------ | ------- | -------- | -------- | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -163,6 +165,7 @@ https://opencode.ai/zen/v1/models 免费模型: - MiniMax M2.5 Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 +- Ling 2.6 Flash Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Nemotron 3 Super Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Big Pickle 是一个隐身模型,目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 @@ -206,6 +209,7 @@ https://opencode.ai/zen/v1/models - Big Pickle:在免费期间,收集的数据可能会被用于改进模型。 - MiniMax M2.5 Free:在免费期间,收集的数据可能会被用于改进模型。 +- Ling 2.6 Flash Free:在免费期间,收集的数据可能会被用于改进模型。 - Nemotron 3 Super Free(NVIDIA 免费端点):根据 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。仅供试用,不适用于生产环境或敏感数据。NVIDIA 会记录提示词和输出内容,以改进其模型和服务。请勿提交个人或机密数据。 - OpenAI APIs:请求会根据 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。 - Anthropic APIs:请求会根据 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 保留 30 天。 diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 5ccc36785..0afaf10b6 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -95,10 +95,11 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode 設定中的 [模型 ID](/docs/config/#models) 會使用 `opencode/` -格式。例如,如果是 GPT 5.3 Codex,你會在設定中使用 `opencode/gpt-5.3-codex`。 +格式。例如,如果是 GPT 5.4,你會在設定中使用 `opencode/gpt-5.4`。 --- @@ -119,8 +120,9 @@ https://opencode.ai/zen/v1/models | 模型 | 輸入 | 輸出 | 快取讀取 | 快取寫入 | | --------------------------------- | ------ | ------- | -------- | -------- | | Big Pickle | Free | Free | Free | - | -| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | +| Ling 2.6 Flash Free | Free | Free | Free | - | +| Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | @@ -169,6 +171,7 @@ https://opencode.ai/zen/v1/models 免費模型: - MiniMax M2.5 Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 +- Ling 2.6 Flash Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Nemotron 3 Super Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Big Pickle 是一個隱身模型,在 OpenCode 上限時免費提供。團隊正在利用這段時間收集回饋並改進模型。 @@ -213,6 +216,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: 在免費期間,收集到的資料可能會用於改進模型。 - MiniMax M2.5 Free: 在免費期間,收集到的資料可能會用於改進模型。 +- Ling 2.6 Flash Free: 在免費期間,收集到的資料可能會用於改進模型。 - Nemotron 3 Super Free(NVIDIA 免費端點):依據 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。僅供試用,不適用於正式環境或敏感資料。NVIDIA 會記錄提示詞與輸出內容,以改進其模型與服務。請勿提交個人或機密資料。 - OpenAI APIs: 請求會依據 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。 - Anthropic APIs: 請求會依據 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 保留 30 天。 From 79336571356e392c2fea9379c041bfbaa9bcebcd Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 17:26:50 -0400 Subject: [PATCH 030/426] migrate LSP data schemas to Effect Schema (#23745) --- packages/opencode/src/lsp/lsp.ts | 97 +++++++++---------- .../src/server/routes/instance/file.ts | 2 +- .../src/server/routes/instance/index.ts | 2 +- packages/opencode/src/session/message-v2.ts | 2 +- 4 files changed, 49 insertions(+), 54 deletions(-) diff --git a/packages/opencode/src/lsp/lsp.ts b/packages/opencode/src/lsp/lsp.ts index aa519f9f7..833285e7b 100644 --- a/packages/opencode/src/lsp/lsp.ts +++ b/packages/opencode/src/lsp/lsp.ts @@ -10,9 +10,11 @@ import { Config } from "../config" import { Flag } from "@/flag/flag" import { Process } from "../util" import { spawn as lspspawn } from "./launch" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema } from "effect" import { InstanceState } from "@/effect" import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { withStatics } from "@/util/schema" +import { zod, ZodOverride } from "@/util/effect-zod" const log = Log.create({ service: "lsp" }) @@ -20,60 +22,53 @@ export const Event = { Updated: BusEvent.define("lsp.updated", z.object({})), } -export const Range = z - .object({ - start: z.object({ - line: z.number(), - character: z.number(), - }), - end: z.object({ - line: z.number(), - character: z.number(), - }), - }) - .meta({ - ref: "Range", - }) -export type Range = z.infer +const Position = Schema.Struct({ + line: Schema.Number, + character: Schema.Number, +}) -export const Symbol = z - .object({ - name: z.string(), - kind: z.number(), - location: z.object({ - uri: z.string(), - range: Range, - }), - }) - .meta({ - ref: "Symbol", - }) -export type Symbol = z.infer +export const Range = Schema.Struct({ + start: Position, + end: Position, +}) + .annotate({ identifier: "Range" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Range = typeof Range.Type -export const DocumentSymbol = z - .object({ - name: z.string(), - detail: z.string().optional(), - kind: z.number(), +export const Symbol = Schema.Struct({ + name: Schema.String, + kind: Schema.Number, + location: Schema.Struct({ + uri: Schema.String, range: Range, - selectionRange: Range, - }) - .meta({ - ref: "DocumentSymbol", - }) -export type DocumentSymbol = z.infer + }), +}) + .annotate({ identifier: "Symbol" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Symbol = typeof Symbol.Type -export const Status = z - .object({ - id: z.string(), - name: z.string(), - root: z.string(), - status: z.union([z.literal("connected"), z.literal("error")]), - }) - .meta({ - ref: "LSPStatus", - }) -export type Status = z.infer +export const DocumentSymbol = Schema.Struct({ + name: Schema.String, + detail: Schema.optional(Schema.String), + kind: Schema.Number, + range: Range, + selectionRange: Range, +}) + .annotate({ identifier: "DocumentSymbol" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type DocumentSymbol = typeof DocumentSymbol.Type + +export const Status = Schema.Struct({ + id: Schema.String, + name: Schema.String, + root: Schema.String, + status: Schema.Literals(["connected", "error"]).annotate({ + [ZodOverride]: z.union([z.literal("connected"), z.literal("error")]), + }), +}) + .annotate({ identifier: "LSPStatus" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Status = typeof Status.Type enum SymbolKind { File = 1, diff --git a/packages/opencode/src/server/routes/instance/file.ts b/packages/opencode/src/server/routes/instance/file.ts index bbef679a8..f92fe6e7e 100644 --- a/packages/opencode/src/server/routes/instance/file.ts +++ b/packages/opencode/src/server/routes/instance/file.ts @@ -90,7 +90,7 @@ export const FileRoutes = lazy(() => description: "Symbols", content: { "application/json": { - schema: resolver(LSP.Symbol.array()), + schema: resolver(LSP.Symbol.zod.array()), }, }, }, diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 0038c5961..e8a038fab 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -260,7 +260,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { description: "LSP server status", content: { "application/json": { - schema: resolver(LSP.Status.array()), + schema: resolver(LSP.Status.zod.array()), }, }, }, diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 20528763b..58bee8d1e 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -159,7 +159,7 @@ export const FileSource = FilePartSourceBase.extend({ export const SymbolSource = FilePartSourceBase.extend({ type: z.literal("symbol"), path: z.string(), - range: LSP.Range, + range: LSP.Range.zod, name: z.string(), kind: z.number().int(), }).meta({ From 2ae64f426b7d210f64151124a3b7d729f28af7ca Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 17:30:08 -0400 Subject: [PATCH 031/426] refactor(core): migrate MessageV2.Format to Effect Schema (#23744) --- packages/opencode/specs/effect/schema.md | 74 +++++++++++++++++++ packages/opencode/src/session/message-v2.ts | 43 +++++------ packages/opencode/src/session/prompt.ts | 2 +- .../test/session/structured-output.test.ts | 12 +-- 4 files changed, 103 insertions(+), 28 deletions(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 2fcbfc12b..6deea4965 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -186,6 +186,80 @@ schema module with a clear domain. Major cluster. Message + event types flow through the SSE API and every SDK output, so byte-identical SDK surface is critical. +Suggested order for this cluster, starting from the leaves that `session.ts` +and the SSE/event surface depend on: + +1. `src/session/schema.ts` ✅ already migrated +2. `src/provider/schema.ts` if `message-v2.ts` still relies on zod-first IDs +3. `src/lsp/*` schema leaves needed by `LSP.Range` +4. `src/snapshot/*` leaves used by `Snapshot.FileDiff` +5. `src/session/message-v2.ts` +6. `src/session/message.ts` +7. `src/session/prompt.ts` +8. `src/session/revert.ts` +9. `src/session/summary.ts` +10. `src/session/status.ts` +11. `src/session/todo.ts` +12. `src/session/session.ts` +13. `src/session/compaction.ts` + +Dependency sketch: + +```text +session.ts +|- project/schema.ts +|- control-plane/schema.ts +|- permission/schema.ts +|- snapshot/* +|- message-v2.ts +| |- provider/schema.ts +| |- lsp/* +| |- snapshot/* +| |- sync/index.ts +| `- bus/bus-event.ts +|- sync/index.ts +|- bus/bus-event.ts +`- util/update-schema.ts +``` + +Working rule for this cluster: + +- migrate reusable leaf schemas and nested payload objects first +- migrate aggregate DTOs like `Session.Info` after their nested pieces exist as + named Schema values +- leave zod-only event/update helpers in place temporarily when converting + them would force unrelated churn across sync/bus boundaries + +`message-v2.ts` first-pass outline: + +1. Schema-backed imports already available + - `SessionID`, `MessageID`, `PartID` + - `ProviderID`, `ModelID` +2. Local leaf objects to extract and migrate first + - output format payloads + - common part bases like `PartBase` + - timestamp/range helper objects like `time.start/end` + - file/source helper objects + - token/cost/model helper objects +3. Part variants built from those leaves + - `SnapshotPart`, `PatchPart`, `TextPart`, `ReasoningPart` + - `FilePart`, `AgentPart`, `CompactionPart`, `SubtaskPart` + - retry/step/tool related parts +4. Higher-level unions and DTOs + - `FilePartSource` + - part unions + - message unions and assistant/user payloads +5. Errors and event payloads last + - `NamedError.create(...)` shapes can stay temporarily if converting them to + `Schema.TaggedErrorClass` would force unrelated churn + - `SyncEvent.define(...)` and `BusEvent.define(...)` payloads can keep using + derived `.zod` until the sync/bus layers are migrated + +Possible later tightening after the Schema-first migration is stable: + +- promote repeated opaque strings and timestamp numbers into branded/newtype + leaf schemas where that adds domain value without changing the wire format + - [ ] `src/session/compaction.ts` - [ ] `src/session/message-v2.ts` - [ ] `src/session/message.ts` diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 58bee8d1e..980e5e2c6 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -15,7 +15,8 @@ import { isMedia } from "@/util/media" import type { SystemError } from "bun" import type { Provider } from "@/provider" import { ModelID, ProviderID } from "@/provider/schema" -import { Effect } from "effect" +import { Effect, Schema } from "effect" +import { zod } from "@/util/effect-zod" import { EffectLogger } from "@/effect" /** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */ @@ -61,28 +62,28 @@ export const ContextOverflowError = NamedError.create( z.object({ message: z.string(), responseBody: z.string().optional() }), ) -export const OutputFormatText = z - .object({ - type: z.literal("text"), - }) - .meta({ - ref: "OutputFormatText", - }) +export class OutputFormatText extends Schema.Class("OutputFormatText")({ + type: Schema.Literal("text"), +}) { + static readonly zod = zod(this) +} -export const OutputFormatJsonSchema = z - .object({ - type: z.literal("json_schema"), - schema: z.record(z.string(), z.any()).meta({ ref: "JSONSchema" }), - retryCount: z.number().int().min(0).default(2), - }) - .meta({ - ref: "OutputFormatJsonSchema", - }) +export class OutputFormatJsonSchema extends Schema.Class("OutputFormatJsonSchema")({ + type: Schema.Literal("json_schema"), + schema: Schema.Record(Schema.String, Schema.Any).annotate({ identifier: "JSONSchema" }), + retryCount: Schema.Number.check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)) + .pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))), +}) { + static readonly zod = zod(this) +} -export const Format = z.discriminatedUnion("type", [OutputFormatText, OutputFormatJsonSchema]).meta({ - ref: "OutputFormat", +const _Format = Schema.Union([OutputFormatText, OutputFormatJsonSchema]).annotate({ + discriminator: "type", + identifier: "OutputFormat", }) -export type OutputFormat = z.infer +export const Format = Object.assign(_Format, { zod: zod(_Format) }) +export type OutputFormat = Schema.Schema.Type const PartBase = z.object({ id: PartID.zod, @@ -360,7 +361,7 @@ export const User = Base.extend({ time: z.object({ created: z.number(), }), - format: Format.optional(), + format: Format.zod.optional(), summary: z .object({ title: z.string().optional(), diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 431189d19..6dcec0459 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1716,7 +1716,7 @@ export const PromptInput = z.object({ .record(z.string(), z.boolean()) .optional() .describe("@deprecated tools and permissions have been merged, you can set permissions on the session itself now"), - format: MessageV2.Format.optional(), + format: MessageV2.Format.zod.optional(), system: z.string().optional(), variant: z.string().optional(), parts: z.array( diff --git a/packages/opencode/test/session/structured-output.test.ts b/packages/opencode/test/session/structured-output.test.ts index 2debfb76d..a91446bf4 100644 --- a/packages/opencode/test/session/structured-output.test.ts +++ b/packages/opencode/test/session/structured-output.test.ts @@ -5,7 +5,7 @@ import { SessionID, MessageID } from "../../src/session/schema" describe("structured-output.OutputFormat", () => { test("parses text format", () => { - const result = MessageV2.Format.safeParse({ type: "text" }) + const result = MessageV2.Format.zod.safeParse({ type: "text" }) expect(result.success).toBe(true) if (result.success) { expect(result.data.type).toBe("text") @@ -13,7 +13,7 @@ describe("structured-output.OutputFormat", () => { }) test("parses json_schema format with defaults", () => { - const result = MessageV2.Format.safeParse({ + const result = MessageV2.Format.zod.safeParse({ type: "json_schema", schema: { type: "object", properties: { name: { type: "string" } } }, }) @@ -27,7 +27,7 @@ describe("structured-output.OutputFormat", () => { }) test("parses json_schema format with custom retryCount", () => { - const result = MessageV2.Format.safeParse({ + const result = MessageV2.Format.zod.safeParse({ type: "json_schema", schema: { type: "object" }, retryCount: 5, @@ -39,17 +39,17 @@ describe("structured-output.OutputFormat", () => { }) test("rejects invalid type", () => { - const result = MessageV2.Format.safeParse({ type: "invalid" }) + const result = MessageV2.Format.zod.safeParse({ type: "invalid" }) expect(result.success).toBe(false) }) test("rejects json_schema without schema", () => { - const result = MessageV2.Format.safeParse({ type: "json_schema" }) + const result = MessageV2.Format.zod.safeParse({ type: "json_schema" }) expect(result.success).toBe(false) }) test("rejects negative retryCount", () => { - const result = MessageV2.Format.safeParse({ + const result = MessageV2.Format.zod.safeParse({ type: "json_schema", schema: { type: "object" }, retryCount: -1, From b0f565b74a7dd21f1f2aba00718c9130524a4d6a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 17:33:13 -0400 Subject: [PATCH 032/426] refactor(core): migrate ConfigPermission.Info to Effect Schema canonical (#23740) --- packages/opencode/src/config/agent.ts | 8 +- packages/opencode/src/config/config.ts | 3 +- packages/opencode/src/config/permission.ts | 73 +++++------ packages/opencode/src/permission/index.ts | 12 +- packages/opencode/src/util/effect-zod.ts | 41 +----- packages/opencode/test/config/config.test.ts | 22 +++- .../opencode/test/permission/next.test.ts | 61 +++++++++ .../opencode/test/util/effect-zod.test.ts | 117 +----------------- packages/sdk/js/src/v2/gen/types.gen.ts | 5 +- 9 files changed, 133 insertions(+), 209 deletions(-) diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index 9755c2037..5a91a3899 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -22,12 +22,6 @@ const Color = Schema.Union([ Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]), ]) -// ConfigPermission.Info is a zod schema (its `.preprocess(...).transform(...)` -// shape lives outside the Effect Schema type system), so the walker reaches it -// via ZodOverride rather than a pure Schema reference. This preserves the -// `$ref: PermissionConfig` emitted in openapi.json. -const PermissionRef = Schema.Any.annotate({ [ZodOverride]: ConfigPermission.Info }) - const AgentSchema = Schema.StructWithRest( Schema.Struct({ model: Schema.optional(ConfigModelID), @@ -54,7 +48,7 @@ const AgentSchema = Schema.StructWithRest( description: "Maximum number of agentic iterations before forcing text-only response", }), maxSteps: Schema.optional(PositiveInt).annotate({ description: "@deprecated Use 'steps' field instead." }), - permission: Schema.optional(PermissionRef), + permission: Schema.optional(ConfigPermission.Info), }), [Schema.Record(Schema.String, Schema.Any)], ) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 4af079127..5423ba3ba 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -86,7 +86,6 @@ export type Layout = ConfigLayout.Layout // ZodOverride-annotated Schema.Any. Walker sees the annotation and emits the // exact zod directly, preserving component $refs. const AgentRef = Schema.Any.annotate({ [ZodOverride]: ConfigAgent.Info }) -const PermissionRef = Schema.Any.annotate({ [ZodOverride]: ConfigPermission.Info }) const LogLevelRef = Schema.Any.annotate({ [ZodOverride]: Log.Level }) const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) @@ -198,7 +197,7 @@ export const Info = Schema.Struct({ description: "Additional instruction files or patterns to include", }), layout: Schema.optional(ConfigLayout.Layout).annotate({ description: "@deprecated Always uses stretch layout." }), - permission: Schema.optional(PermissionRef), + permission: Schema.optional(ConfigPermission.Info), tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)), enterprise: Schema.optional( Schema.Struct({ diff --git a/packages/opencode/src/config/permission.ts b/packages/opencode/src/config/permission.ts index d4883ed8c..fdd574683 100644 --- a/packages/opencode/src/config/permission.ts +++ b/packages/opencode/src/config/permission.ts @@ -1,6 +1,6 @@ export * as ConfigPermission from "./permission" -import { Schema } from "effect" -import { zod, ZodPreprocess } from "@/util/effect-zod" +import { Schema, SchemaGetter } from "effect" +import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" export const Action = Schema.Literals(["ask", "allow", "deny"]) @@ -18,21 +18,19 @@ export const Rule = Schema.Union([Action, Object]) .pipe(withStatics((s) => ({ zod: zod(s) }))) export type Rule = Schema.Schema.Type -// Captures the user's original property insertion order before Schema.Struct -// canonicalises the object. See the `ZodPreprocess` comment in -// `util/effect-zod.ts` for the full rationale — in short: rule precedence is -// encoded in JSON key order (`evaluate.ts` uses `findLast`, so later keys win) -// and `Schema.StructWithRest` would otherwise drop that order. -const permissionPreprocess = (val: unknown) => { - if (typeof val === "object" && val !== null && !Array.isArray(val)) { - return { __originalKeys: globalThis.Object.keys(val), ...val } - } - return val -} - -const ObjectShape = Schema.StructWithRest( +// Known permission keys get explicit types — most are full Rule (either a +// single Action or a per-pattern object), but a handful of tools take no +// sub-target patterns and are Action-only. Unknown keys fall through the +// Record rest signature as Rule. +// +// StructWithRest canonicalises key order on decode (known first, then rest), +// which used to require the `__originalKeys` preprocess hack because +// `Permission.fromConfig` depended on the user's insertion order. That +// dependency is gone — `fromConfig` now sorts top-level keys so wildcard +// permissions come before specifics, making the final precedence +// order-independent. +const InputObject = Schema.StructWithRest( Schema.Struct({ - __originalKeys: Schema.optional(Schema.mutable(Schema.Array(Schema.String))), read: Schema.optional(Rule), edit: Schema.optional(Rule), glob: Schema.optional(Rule), @@ -53,24 +51,29 @@ const ObjectShape = Schema.StructWithRest( [Schema.Record(Schema.String, Rule)], ) -const InnerSchema = Schema.Union([ObjectShape, Action]).annotate({ - [ZodPreprocess]: permissionPreprocess, -}) +// Input the user writes in config: either a single Action (shorthand for "*") +// or an object of per-target rules. +const InputSchema = Schema.Union([Action, InputObject]) -// Post-parse: drop the __originalKeys metadata and rebuild the rule map in the -// user's original insertion order. A plain string input (the Action branch of -// the union) becomes `{ "*": action }`. -const transform = (x: unknown): Record => { - if (typeof x === "string") return { "*": x as Action } - const obj = x as { __originalKeys?: string[] } & Record - const { __originalKeys, ...rest } = obj - if (!__originalKeys) return rest as Record - const result: Record = {} - for (const key of __originalKeys) { - if (key in rest) result[key] = rest[key] as Rule - } - return result -} +// Normalise the Action shorthand into `{ "*": action }`. Object inputs pass +// through untouched. +const normalizeInput = (input: Schema.Schema.Type): Schema.Schema.Type => + typeof input === "string" ? { "*": input } : input -export const Info = zod(InnerSchema).transform(transform).meta({ ref: "PermissionConfig" }) -export type Info = Record +export const Info = InputSchema.pipe( + Schema.decodeTo(InputObject, { + decode: SchemaGetter.transform(normalizeInput), + // Not perfectly invertible (we lose whether the user originally typed an + // Action shorthand), but the object form is always a valid representation + // of the same rules. + encode: SchemaGetter.passthrough({ strict: false }), + }), +) + .annotate({ identifier: "PermissionConfig" }) + .pipe( + // Walker already emits the decodeTo transform into the derived zod (see + // `encoded()` in effect-zod.ts), so just expose that directly. + withStatics((s) => ({ zod: zod(s) })), + ) +type _Info = Schema.Schema.Type +export type Info = { -readonly [K in keyof _Info]: _Info[K] } diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index caf66cc94..6943b3d93 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -290,8 +290,18 @@ function expand(pattern: string): string { } export function fromConfig(permission: ConfigPermission.Info) { + // Sort top-level keys so wildcard permissions (`*`, `mcp_*`) come before + // specific ones. Combined with `findLast` in evaluate(), this gives the + // intuitive semantic "specific tool rules override the `*` fallback" + // regardless of the user's JSON key order. Sub-pattern order inside a + // single permission key is preserved — only top-level keys are sorted. + const entries = Object.entries(permission).sort(([a], [b]) => { + const aWild = a.includes("*") + const bWild = b.includes("*") + return aWild === bWild ? 0 : aWild ? -1 : 1 + }) const ruleset: Ruleset = [] - for (const [key, value] of Object.entries(permission)) { + for (const [key, value] of entries) { if (typeof value === "string") { ruleset.push({ permission: key, action: value, pattern: "*" }) continue diff --git a/packages/opencode/src/util/effect-zod.ts b/packages/opencode/src/util/effect-zod.ts index bf1caa035..f6d2c5e5c 100644 --- a/packages/opencode/src/util/effect-zod.ts +++ b/packages/opencode/src/util/effect-zod.ts @@ -8,43 +8,6 @@ import z from "zod" */ export const ZodOverride: unique symbol = Symbol.for("effect-zod/override") -/** - * Annotation key for a pre-parse transform that runs on the raw input before - * the derived Zod schema validates it. The walker emits - * `z.preprocess(fn, inner)` when this annotation is present. - * - * Models zod's `z.preprocess(fn, schema)` pattern — useful when the schema - * needs to inspect the user's raw input (e.g. to capture insertion order) - * before `Schema.Struct` canonicalises the object. - * - * TODO: This exists to paper over a missing Effect Schema feature. The - * parser canonicalises open struct output (known fields first in - * declaration order, then catchall fields) before any user-defined - * transform sees the value, and there is no pre-parse hook — so the - * user's original property insertion order is gone by the time - * `Schema.decodeTo` or `middlewareDecoding` runs. - * - * That canonicalisation is a reasonable default, but `config/permission.ts` - * encodes rule precedence in the user's JSON key order (`evaluate.ts` - * uses `findLast`, so later entries win), which the canonicalisation - * silently destroys. - * - * The cleanest upstream fix would be either: - * - * 1. A `preserveInputOrder` option on `Schema.Struct` / - * `Schema.StructWithRest` that keeps the input's insertion order in - * the parsed object (opt-in; canonical order stays default). - * 2. A generic pre-parse hook (`Schema.preprocess(schema, fn)` or a - * transformation whose decode receives the raw `unknown`). - * - * Either of those would let us delete `ZodPreprocess` and the - * `__originalKeys` hack. Alternatively, the permission model could move - * to specificity-based precedence (exact keys beat wildcards) or an - * explicit ordered array of rules, which removes the ordering - * dependency at the data-model level. - */ -export const ZodPreprocess: unique symbol = Symbol.for("effect-zod/preprocess") - // AST nodes are immutable and frequently shared across schemas (e.g. a single // Schema.Class embedded in multiple parents). Memoizing by node identity // avoids rebuilding equivalent Zod subtrees and keeps derived children stable @@ -85,11 +48,9 @@ function walkUncached(ast: SchemaAST.AST): z.ZodTypeAny { const hasTransform = hasEncoding && !(SchemaAST.isOptional(ast) && extractDefault(ast) !== undefined) const base = hasTransform ? encoded(ast) : body(ast) const checked = ast.checks?.length ? applyChecks(base, ast.checks, ast) : base - const preprocess = (ast.annotations as { [ZodPreprocess]?: (val: unknown) => unknown } | undefined)?.[ZodPreprocess] - const out = preprocess ? z.preprocess(preprocess, checked) : checked const desc = SchemaAST.resolveDescription(ast) const ref = SchemaAST.resolveIdentifier(ast) - const described = desc ? out.describe(desc) : out + const described = desc ? checked.describe(desc) : checked return ref ? described.meta({ ref }) : described } diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index e9b053819..73dd46e31 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1495,7 +1495,16 @@ test("merges legacy tools with existing permission config", async () => { }) }) -test("permission config preserves key order", async () => { +test("permission config canonicalises known keys first, preserves rest-key insertion order", async () => { + // ConfigPermission.Info is a StructWithRest schema — the decoder reorders + // keys into declaration-order for known permission names (edit, read, + // todowrite, external_directory are declared in `config/permission.ts`), + // followed by rest keys in the user's insertion order. + // + // Rule precedence is NOT affected by this reordering: `Permission.fromConfig` + // sorts wildcards before specifics before iterating. See the + // "fromConfig - specific key beats wildcard regardless of JSON key order" + // test in test/permission/next.test.ts for the behavioural guarantee. await using tmp = await tmpdir({ init: async (dir) => { await Filesystem.write( @@ -1523,12 +1532,15 @@ test("permission config preserves key order", async () => { fn: async () => { const config = await load() expect(Object.keys(config.permission!)).toEqual([ - "*", - "edit", - "write", - "external_directory", + // known fields that the user provided, in declaration order from + // config/permission.ts (read, edit, ..., external_directory, todowrite) "read", + "edit", + "external_directory", "todowrite", + // rest keys (not in the known list), in user's insertion order + "*", + "write", "thoughts_*", "reasoning_model_*", "tools_*", diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index 21a9d8400..372e1be7e 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -128,6 +128,67 @@ test("fromConfig - does not expand tilde in middle of path", () => { expect(result).toEqual([{ permission: "external_directory", pattern: "/some/~/path", action: "allow" }]) }) +// Top-level wildcard-vs-specific precedence semantics. +// +// fromConfig sorts top-level keys so wildcard permissions (containing "*") +// come before specific permissions. Combined with `findLast` in evaluate(), +// this gives the intuitive semantic "specific tool rules override the `*` +// fallback", regardless of the order the user wrote the keys in their JSON. +// +// Sub-pattern order inside a single permission key (e.g. `bash: { "*": "allow", "rm": "deny" }`) +// still depends on insertion order — only top-level keys are sorted. + +test("fromConfig - specific key beats wildcard regardless of JSON key order", () => { + const wildcardFirst = Permission.fromConfig({ "*": "deny", bash: "allow" }) + const specificFirst = Permission.fromConfig({ bash: "allow", "*": "deny" }) + + // Both orderings produce the same ruleset + expect(wildcardFirst).toEqual(specificFirst) + + // And both evaluate bash → allow (bash rule wins over * fallback) + expect(Permission.evaluate("bash", "ls", wildcardFirst).action).toBe("allow") + expect(Permission.evaluate("bash", "ls", specificFirst).action).toBe("allow") +}) + +test("fromConfig - wildcard acts as fallback for permissions with no specific rule", () => { + const ruleset = Permission.fromConfig({ bash: "allow", "*": "ask" }) + expect(Permission.evaluate("edit", "foo.ts", ruleset).action).toBe("ask") + expect(Permission.evaluate("bash", "ls", ruleset).action).toBe("allow") +}) + +test("fromConfig - top-level ordering: wildcards first, specifics after", () => { + const ruleset = Permission.fromConfig({ + bash: "allow", + "*": "ask", + edit: "deny", + "mcp_*": "allow", + }) + // wildcards (* and mcp_*) come before specifics (bash, edit) + const permissions = ruleset.map((r) => r.permission) + expect(permissions.slice(0, 2).sort()).toEqual(["*", "mcp_*"]) + expect(permissions.slice(2)).toEqual(["bash", "edit"]) +}) + +test("fromConfig - sub-pattern insertion order inside a tool key is preserved (only top-level sorts)", () => { + // Sub-patterns within a single tool key use the documented "`*` first, + // specific patterns after" convention (findLast picks specifics). The + // top-level sort must not touch sub-pattern ordering. + const ruleset = Permission.fromConfig({ bash: { "*": "deny", "git *": "allow" } }) + expect(ruleset.map((r) => r.pattern)).toEqual(["*", "git *"]) + // * fallback for unknown commands + expect(Permission.evaluate("bash", "rm foo", ruleset).action).toBe("deny") + // specific pattern wins for git commands (it's last, findLast picks it) + expect(Permission.evaluate("bash", "git status", ruleset).action).toBe("allow") +}) + +test("fromConfig - canonical documented example unchanged", () => { + // Regression guard for the example in docs/permissions.mdx + const ruleset = Permission.fromConfig({ "*": "ask", bash: "allow", edit: "deny" }) + expect(Permission.evaluate("bash", "ls", ruleset).action).toBe("allow") + expect(Permission.evaluate("edit", "foo.ts", ruleset).action).toBe("deny") + expect(Permission.evaluate("read", "foo.ts", ruleset).action).toBe("ask") +}) + test("fromConfig - expands exact tilde to home directory", () => { const result = Permission.fromConfig({ external_directory: { "~": "allow" } }) expect(result).toEqual([{ permission: "external_directory", pattern: os.homedir(), action: "allow" }]) diff --git a/packages/opencode/test/util/effect-zod.test.ts b/packages/opencode/test/util/effect-zod.test.ts index 003945b43..70cd8f0e6 100644 --- a/packages/opencode/test/util/effect-zod.test.ts +++ b/packages/opencode/test/util/effect-zod.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test" import { Effect, Schema, SchemaGetter } from "effect" import z from "zod" -import { zod, ZodOverride, ZodPreprocess } from "../../src/util/effect-zod" +import { zod, ZodOverride } from "../../src/util/effect-zod" function json(schema: z.ZodTypeAny) { const { $schema: _, ...rest } = z.toJSONSchema(schema) @@ -751,119 +751,4 @@ describe("util.effect-zod", () => { expect(schema.parse({ foo: "hi" })).toEqual({ foo: "hi" }) }) }) - - describe("ZodPreprocess annotation", () => { - test("preprocess runs on raw input before the inner schema parses", () => { - // Models the permission.ts __originalKeys pattern: capture the original - // insertion order of a user-provided object BEFORE Schema parsing - // canonicalises the keys. - const preprocess = (val: unknown) => { - if (typeof val === "object" && val !== null && !Array.isArray(val)) { - return { __keys: Object.keys(val), ...(val as Record) } - } - return val - } - const Inner = Schema.Struct({ - __keys: Schema.optional(Schema.mutable(Schema.Array(Schema.String))), - a: Schema.optional(Schema.String), - b: Schema.optional(Schema.String), - }).annotate({ [ZodPreprocess]: preprocess }) - - const schema = zod(Inner) - const parsed = schema.parse({ b: "1", a: "2" }) as { - __keys?: string[] - a?: string - b?: string - } - expect(parsed.__keys).toEqual(["b", "a"]) - expect(parsed.a).toBe("2") - expect(parsed.b).toBe("1") - }) - - test("preprocess does not transform already-shaped input", () => { - // When the user passes an object that already has __keys, preprocess - // returns it unchanged because spreading preserves any existing key. - const preprocess = (val: unknown) => { - if (typeof val === "object" && val !== null && !("__keys" in val)) { - return { __keys: Object.keys(val), ...(val as Record) } - } - return val - } - const Inner = Schema.Struct({ - __keys: Schema.optional(Schema.mutable(Schema.Array(Schema.String))), - a: Schema.optional(Schema.String), - }).annotate({ [ZodPreprocess]: preprocess }) - - const schema = zod(Inner) - const parsed = schema.parse({ __keys: ["existing"], a: "hi" }) as { - __keys?: string[] - a?: string - } - expect(parsed.__keys).toEqual(["existing"]) - }) - - test("preprocess composes with a union (either object or string)", () => { - // Mirrors permission.ts exactly: input can be either an object (with - // preprocess injecting metadata) or a plain string action. - const Action = Schema.Literals(["ask", "allow", "deny"]) - const Obj = Schema.Struct({ - __keys: Schema.optional(Schema.mutable(Schema.Array(Schema.String))), - read: Schema.optional(Action), - write: Schema.optional(Action), - }) - const preprocess = (val: unknown) => { - if (typeof val === "object" && val !== null && !Array.isArray(val)) { - return { __keys: Object.keys(val), ...(val as Record) } - } - return val - } - const Inner = Schema.Union([Obj, Action]).annotate({ [ZodPreprocess]: preprocess }) - const schema = zod(Inner) - - // String branch — passes through preprocess unchanged - expect(schema.parse("allow")).toBe("allow") - - // Object branch — __keys injected, preserves order - const parsed = schema.parse({ write: "allow", read: "deny" }) as { - __keys?: string[] - read?: string - write?: string - } - expect(parsed.__keys).toEqual(["write", "read"]) - expect(parsed.write).toBe("allow") - expect(parsed.read).toBe("deny") - }) - - test("JSON Schema output comes from the inner schema — preprocess is runtime-only", () => { - const Inner = Schema.Struct({ - a: Schema.optional(Schema.String), - b: Schema.optional(Schema.Number), - }).annotate({ [ZodPreprocess]: (v: unknown) => v }) - const shape = json(zod(Inner)) as any - expect(shape.type).toBe("object") - expect(shape.properties.a.type).toBe("string") - expect(shape.properties.b.type).toBe("number") - }) - - test("identifier + description propagate through the preprocess wrapper", () => { - const Inner = Schema.Struct({ - x: Schema.optional(Schema.String), - }).annotate({ - identifier: "WithPreproc", - description: "A schema with preprocess", - [ZodPreprocess]: (v: unknown) => v, - }) - const schema = zod(Inner) - expect(schema.meta()?.ref).toBe("WithPreproc") - expect(schema.meta()?.description).toBe("A schema with preprocess") - }) - - test("preprocess inside a struct field applies only to that field", () => { - const Inner = Schema.String.annotate({ - [ZodPreprocess]: (v: unknown) => (typeof v === "number" ? String(v) : v), - }) - const schema = zod(Schema.Struct({ name: Inner, raw: Schema.Number })) - expect(schema.parse({ name: 42, raw: 7 })).toEqual({ name: "42", raw: 7 }) - }) - }) }) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index d14fab191..1fcab2eda 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1205,8 +1205,8 @@ export type PermissionObjectConfig = { export type PermissionRuleConfig = PermissionActionConfig | PermissionObjectConfig export type PermissionConfig = + | PermissionActionConfig | { - __originalKeys?: Array read?: PermissionRuleConfig edit?: PermissionRuleConfig glob?: PermissionRuleConfig @@ -1223,9 +1223,8 @@ export type PermissionConfig = lsp?: PermissionRuleConfig doom_loop?: PermissionActionConfig skill?: PermissionRuleConfig - [key: string]: PermissionRuleConfig | Array | PermissionActionConfig | undefined + [key: string]: PermissionRuleConfig | PermissionActionConfig | undefined } - | PermissionActionConfig export type AgentConfig = { model?: string From b1c3095edd901a74aa3ed94b5b6bffe6a4217b24 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 21 Apr 2026 21:34:17 +0000 Subject: [PATCH 033/426] chore: generate --- packages/sdk/openapi.json | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index dbd85874f..d9954d915 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -10980,15 +10980,12 @@ }, "PermissionConfig": { "anyOf": [ + { + "$ref": "#/components/schemas/PermissionActionConfig" + }, { "type": "object", "properties": { - "__originalKeys": { - "type": "array", - "items": { - "type": "string" - } - }, "read": { "$ref": "#/components/schemas/PermissionRuleConfig" }, @@ -11041,9 +11038,6 @@ "additionalProperties": { "$ref": "#/components/schemas/PermissionRuleConfig" } - }, - { - "$ref": "#/components/schemas/PermissionActionConfig" } ] }, From 0bcf734a67e3b46b26f9c68d1800086365ed67e7 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 17:37:27 -0400 Subject: [PATCH 034/426] migrate Snapshot schemas to Effect Schema (#23747) --- .../src/server/routes/instance/session.ts | 2 +- packages/opencode/src/session/message-v2.ts | 2 +- packages/opencode/src/session/session.ts | 4 +-- packages/opencode/src/snapshot/index.ts | 36 +++++++++---------- packages/opencode/src/tool/edit.ts | 14 ++++---- 5 files changed, 30 insertions(+), 28 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/session.ts b/packages/opencode/src/server/routes/instance/session.ts index bf713935b..a46c2f3bf 100644 --- a/packages/opencode/src/server/routes/instance/session.ts +++ b/packages/opencode/src/server/routes/instance/session.ts @@ -471,7 +471,7 @@ export const SessionRoutes = lazy(() => description: "Successfully retrieved diff", content: { "application/json": { - schema: resolver(Snapshot.FileDiff.array()), + schema: resolver(Snapshot.FileDiff.zod.array()), }, }, }, diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 980e5e2c6..83477d12b 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -366,7 +366,7 @@ export const User = Base.extend({ .object({ title: z.string().optional(), body: z.string().optional(), - diffs: Snapshot.FileDiff.array(), + diffs: Snapshot.FileDiff.zod.array(), }) .optional(), agent: z.string(), diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index ba144da9f..6e9fb5c5d 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -127,7 +127,7 @@ export const Info = z additions: z.number(), deletions: z.number(), files: z.number(), - diffs: Snapshot.FileDiff.array().optional(), + diffs: Snapshot.FileDiff.zod.array().optional(), }) .optional(), share: z @@ -239,7 +239,7 @@ export const Event = { "session.diff", z.object({ sessionID: SessionID.zod, - diff: Snapshot.FileDiff.array(), + diff: Snapshot.FileDiff.zod.array(), }), ), Error: BusEvent.define( diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index d38034e99..ddc4cb29e 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -1,4 +1,4 @@ -import { Cause, Duration, Effect, Layer, Schedule, Semaphore, Context, Stream } from "effect" +import { Cause, Duration, Effect, Layer, Schedule, Schema, Semaphore, Context, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { formatPatch, structuredPatch } from "diff" import path from "path" @@ -10,25 +10,25 @@ import { Hash } from "@opencode-ai/shared/util/hash" import { Config } from "../config" import { Global } from "../global" import { Log } from "../util" +import { withStatics } from "@/util/schema" +import { zod } from "@/util/effect-zod" -export const Patch = z.object({ - hash: z.string(), - files: z.string().array(), +export const Patch = Schema.Struct({ + hash: Schema.String, + files: Schema.mutable(Schema.Array(Schema.String)), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Patch = typeof Patch.Type + +export const FileDiff = Schema.Struct({ + file: Schema.String, + patch: Schema.String, + additions: Schema.Number, + deletions: Schema.Number, + status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])), }) -export type Patch = z.infer - -export const FileDiff = z - .object({ - file: z.string(), - patch: z.string(), - additions: z.number(), - deletions: z.number(), - status: z.enum(["added", "deleted", "modified"]).optional(), - }) - .meta({ - ref: "SnapshotFileDiff", - }) -export type FileDiff = z.infer + .annotate({ identifier: "SnapshotFileDiff" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type FileDiff = typeof FileDiff.Type const log = Log.create({ service: "snapshot" }) const prune = "7.days" diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 2f53cd194..2c6c2c130 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -153,15 +153,17 @@ export const EditTool = Tool.define( }).pipe(Effect.orDie), ) + let additions = 0 + let deletions = 0 + for (const change of diffLines(contentOld, contentNew)) { + if (change.added) additions += change.count || 0 + if (change.removed) deletions += change.count || 0 + } const filediff: Snapshot.FileDiff = { file: filePath, patch: diff, - additions: 0, - deletions: 0, - } - for (const change of diffLines(contentOld, contentNew)) { - if (change.added) filediff.additions += change.count || 0 - if (change.removed) filediff.deletions += change.count || 0 + additions, + deletions, } yield* ctx.metadata({ From d6dea3f3e00598a734d2fb61dbc9d74ccbd1781c Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 17:40:54 -0400 Subject: [PATCH 035/426] chore(core): clean up after ConfigPermission Effect Schema migration (#23749) --- packages/opencode/specs/effect/schema.md | 16 ++-------------- packages/opencode/src/config/agent.ts | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 6deea4965..df3cc0881 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -97,7 +97,7 @@ creating a parallel schema source of truth. ## Escape hatches -The walker in `@/util/effect-zod` exposes three explicit escape hatches for +The walker in `@/util/effect-zod` exposes two explicit escape hatches for cases the pure-Schema path cannot express. Each one stays in the codebase only as long as its upstream or local dependency requires it — inline comments document when each can be deleted. @@ -109,19 +109,7 @@ Replaces the entire derivation with a hand-crafted zod schema. Used when: - the target carries external `$ref` metadata (e.g. `config/model-id.ts` points at `https://models.dev/...`) - the target is a zod-only schema that cannot yet be expressed as Schema - (e.g. `ConfigAgent.Info`, `ConfigPermission.Info`, `Log.Level`) - -### `ZodPreprocess` annotation - -Wraps the derived zod schema with `z.preprocess(fn, inner)`. Used by -`config/permission.ts` to inject `__originalKeys` before parsing, because -`Schema.StructWithRest` canonicalises output (known fields first, catchall -after) and destroys the user's original property order — which permission -rule precedence depends on. - -Tracked upstream as `effect:core/wlh553`: "Schema: add preserveInputOrder -(or pre-parse hook) for open structs." Once that lands, `ZodPreprocess` and -the `__originalKeys` hack can both be deleted. + (e.g. `ConfigAgent.Info`, `Log.Level`) ### Local `DeepMutable` in `config/config.ts` diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index 5a91a3899..85021407c 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -3,7 +3,7 @@ export * as ConfigAgent from "./agent" import { Schema } from "effect" import z from "zod" import { Bus } from "@/bus" -import { zod, ZodOverride } from "@/util/effect-zod" +import { zod } from "@/util/effect-zod" import { Log } from "../util" import { NamedError } from "@opencode-ai/shared/util/error" import { Glob } from "@opencode-ai/shared/util/glob" From df0c1f649c306bd1c125c0b532bd17b76bf888c0 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 17:47:50 -0400 Subject: [PATCH 036/426] refactor(core): migrate MessageV2 tool state schemas to Effect Schema (#23752) --- packages/opencode/src/session/message-v2.ts | 138 ++++++++++---------- 1 file changed, 72 insertions(+), 66 deletions(-) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 83477d12b..04cb15ef8 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -15,8 +15,9 @@ import { isMedia } from "@/util/media" import type { SystemError } from "bun" import type { Provider } from "@/provider" import { ModelID, ProviderID } from "@/provider/schema" -import { Effect, Schema } from "effect" -import { zod } from "@/util/effect-zod" +import { Effect, Schema, Types } from "effect" +import { zod, ZodOverride } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import { EffectLogger } from "@/effect" /** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */ @@ -272,79 +273,84 @@ export const StepFinishPart = PartBase.extend({ }) export type StepFinishPart = z.infer -export const ToolStatePending = z - .object({ - status: z.literal("pending"), - input: z.record(z.string(), z.any()), - raw: z.string(), - }) - .meta({ - ref: "ToolStatePending", - }) +export const ToolStatePending = Schema.Struct({ + status: Schema.Literal("pending"), + input: Schema.Record(Schema.String, Schema.Any), + raw: Schema.String, +}) + .annotate({ identifier: "ToolStatePending" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolStatePending = Types.DeepMutable> -export type ToolStatePending = z.infer +export const ToolStateRunning = Schema.Struct({ + status: Schema.Literal("running"), + input: Schema.Record(Schema.String, Schema.Any), + title: Schema.optional(Schema.String), + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), + time: Schema.Struct({ + start: Schema.Number, + }), +}) + .annotate({ identifier: "ToolStateRunning" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolStateRunning = Types.DeepMutable> -export const ToolStateRunning = z - .object({ - status: z.literal("running"), - input: z.record(z.string(), z.any()), - title: z.string().optional(), - metadata: z.record(z.string(), z.any()).optional(), - time: z.object({ - start: z.number(), - }), - }) - .meta({ - ref: "ToolStateRunning", - }) -export type ToolStateRunning = z.infer +export const ToolStateCompleted = Schema.Struct({ + status: Schema.Literal("completed"), + input: Schema.Record(Schema.String, Schema.Any), + output: Schema.String, + title: Schema.String, + metadata: Schema.Record(Schema.String, Schema.Any), + time: Schema.Struct({ + start: Schema.Number, + end: Schema.Number, + compacted: Schema.optional(Schema.Number), + }), + // FilePart is still Zod-first this slice; bridge via ZodOverride so the + // derived Zod + JSON Schema still emit `$ref: FilePart` array items. + attachments: Schema.optional(Schema.Any.annotate({ [ZodOverride]: FilePart.array() })), +}) + .annotate({ identifier: "ToolStateCompleted" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolStateCompleted = Omit< + Types.DeepMutable>, + "attachments" +> & { + attachments?: FilePart[] +} -export const ToolStateCompleted = z - .object({ - status: z.literal("completed"), - input: z.record(z.string(), z.any()), - output: z.string(), - title: z.string(), - metadata: z.record(z.string(), z.any()), - time: z.object({ - start: z.number(), - end: z.number(), - compacted: z.number().optional(), - }), - attachments: FilePart.array().optional(), - }) - .meta({ - ref: "ToolStateCompleted", - }) -export type ToolStateCompleted = z.infer +export const ToolStateError = Schema.Struct({ + status: Schema.Literal("error"), + input: Schema.Record(Schema.String, Schema.Any), + error: Schema.String, + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), + time: Schema.Struct({ + start: Schema.Number, + end: Schema.Number, + }), +}) + .annotate({ identifier: "ToolStateError" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolStateError = Types.DeepMutable> -export const ToolStateError = z - .object({ - status: z.literal("error"), - input: z.record(z.string(), z.any()), - error: z.string(), - metadata: z.record(z.string(), z.any()).optional(), - time: z.object({ - start: z.number(), - end: z.number(), - }), - }) - .meta({ - ref: "ToolStateError", - }) -export type ToolStateError = z.infer - -export const ToolState = z - .discriminatedUnion("status", [ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]) - .meta({ - ref: "ToolState", - }) +const _ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]).annotate({ + discriminator: "status", + identifier: "ToolState", +}) +// Cast the derived zod so downstream z.infer sees the same mutable shape that +// our exported TS types expose (the pre-migration Zod inferences were mutable). +export const ToolState = Object.assign(_ToolState, { + zod: zod(_ToolState) as unknown as z.ZodType< + ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError + >, +}) +export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError export const ToolPart = PartBase.extend({ type: z.literal("tool"), callID: z.string(), tool: z.string(), - state: ToolState, + state: ToolState.zod, metadata: z.record(z.string(), z.any()).optional(), }).meta({ ref: "ToolPart", From 2da6d860e0e2c5309a0030a5591c8218fffd6a99 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 17:49:24 -0400 Subject: [PATCH 037/426] refactor(core): derive provider schema .zod via effect-zod walker (#23753) --- packages/opencode/specs/effect/schema.md | 2 +- packages/opencode/src/provider/schema.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index df3cc0881..3ed0b825d 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -162,7 +162,7 @@ schema module with a clear domain. - [ ] `src/control-plane/schema.ts` - [ ] `src/permission/schema.ts` - [ ] `src/project/schema.ts` -- [ ] `src/provider/schema.ts` +- [x] `src/provider/schema.ts` - [ ] `src/pty/schema.ts` - [ ] `src/question/schema.ts` - [ ] `src/session/schema.ts` diff --git a/packages/opencode/src/provider/schema.ts b/packages/opencode/src/provider/schema.ts index 702616018..ea3cac342 100644 --- a/packages/opencode/src/provider/schema.ts +++ b/packages/opencode/src/provider/schema.ts @@ -1,6 +1,6 @@ import { Schema } from "effect" -import z from "zod" +import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" const providerIdSchema = Schema.String.pipe(Schema.brand("ProviderID")) @@ -9,7 +9,7 @@ export type ProviderID = typeof providerIdSchema.Type export const ProviderID = providerIdSchema.pipe( withStatics((schema: typeof providerIdSchema) => ({ - zod: z.string().pipe(z.custom()), + zod: zod(schema), // Well-known providers opencode: schema.make("opencode"), anthropic: schema.make("anthropic"), @@ -30,7 +30,7 @@ const modelIdSchema = Schema.String.pipe(Schema.brand("ModelID")) export type ModelID = typeof modelIdSchema.Type export const ModelID = modelIdSchema.pipe( - withStatics((_schema: typeof modelIdSchema) => ({ - zod: z.string().pipe(z.custom()), + withStatics((schema: typeof modelIdSchema) => ({ + zod: zod(schema), })), ) From 5e9fb3cc9045f9ef9a5c33ff6c295cf0438da39b Mon Sep 17 00:00:00 2001 From: Mathews Bryan <37884221+jmbryan4@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:03:09 -0500 Subject: [PATCH 038/426] feat: replace csharp-ls with roslyn-language-server (#14463) Co-authored-by: Mathews --- packages/opencode/src/lsp/server.ts | 34 +++++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 918236806..a07e33686 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -705,32 +705,42 @@ export const CSharp: Info = { root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"]), extensions: [".cs"], async spawn(root) { - let bin = which("csharp-ls") + let bin = which("roslyn-language-server") if (!bin) { if (!which("dotnet")) { - log.error(".NET SDK is required to install csharp-ls") + log.error(".NET SDK is required to install roslyn-language-server") return } if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - log.info("installing csharp-ls via dotnet tool") - const proc = Process.spawn(["dotnet", "tool", "install", "csharp-ls", "--tool-path", Global.Path.bin], { - stdout: "pipe", - stderr: "pipe", - stdin: "pipe", - }) + log.info("installing roslyn-language-server via dotnet tool") + const proc = Process.spawn( + [ + "dotnet", + "tool", + "install", + "--global", + "roslyn-language-server", + "--prerelease", + ], + { + stdout: "pipe", + stderr: "pipe", + stdin: "pipe", + }, + ) const exit = await proc.exited if (exit !== 0) { - log.error("Failed to install csharp-ls") + log.error("Failed to install roslyn-language-server") return } - bin = path.join(Global.Path.bin, "csharp-ls" + (process.platform === "win32" ? ".exe" : "")) - log.info(`installed csharp-ls`, { bin }) + bin = path.join(Global.Path.bin, "roslyn-language-server" + (process.platform === "win32" ? ".exe" : "")) + log.info(`installed roslyn-language-server`, { bin }) } return { - process: spawn(bin, { + process: spawn(bin, ["--stdio", "--autoLoadProjects"], { cwd: root, }), } From d2181e9273bfcd9727a387527b25aa017ca15410 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 21 Apr 2026 22:04:16 +0000 Subject: [PATCH 039/426] chore: generate --- packages/opencode/src/lsp/server.ts | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index a07e33686..8bb70a511 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -714,21 +714,11 @@ export const CSharp: Info = { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return log.info("installing roslyn-language-server via dotnet tool") - const proc = Process.spawn( - [ - "dotnet", - "tool", - "install", - "--global", - "roslyn-language-server", - "--prerelease", - ], - { - stdout: "pipe", - stderr: "pipe", - stdin: "pipe", - }, - ) + const proc = Process.spawn(["dotnet", "tool", "install", "--global", "roslyn-language-server", "--prerelease"], { + stdout: "pipe", + stderr: "pipe", + stdin: "pipe", + }) const exit = await proc.exited if (exit !== 0) { log.error("Failed to install roslyn-language-server") From 8043cfa68dcf97547ede3e26a9325af55583e1e4 Mon Sep 17 00:00:00 2001 From: NN708 Date: Wed, 22 Apr 2026 07:19:04 +0800 Subject: [PATCH 040/426] fix(desktop): update desktop file and MetaInfo file (#14933) --- packages/desktop/src-tauri/release/appstream.metainfo.xml | 5 ++++- packages/desktop/src-tauri/tauri.conf.json | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/desktop/src-tauri/release/appstream.metainfo.xml b/packages/desktop/src-tauri/release/appstream.metainfo.xml index 16aa2bfcb..d7dd49081 100644 --- a/packages/desktop/src-tauri/release/appstream.metainfo.xml +++ b/packages/desktop/src-tauri/release/appstream.metainfo.xml @@ -28,11 +28,14 @@ - https://opencode.ai/docs/_astro/screenshot.Bs5D4atL_ZvsvFu.webp + https://raw.githubusercontent.com/anomalyco/opencode/b75d4d1c5ec449585d515c756fc81f080a157a9a/packages/web/src/assets/lander/screenshot.png + + https://github.com/anomalyco/opencode/releases/tag/v1.4.0 + https://github.com/anomalyco/opencode/releases/tag/v1.0.223 diff --git a/packages/desktop/src-tauri/tauri.conf.json b/packages/desktop/src-tauri/tauri.conf.json index 265044625..c4f125a27 100644 --- a/packages/desktop/src-tauri/tauri.conf.json +++ b/packages/desktop/src-tauri/tauri.conf.json @@ -32,6 +32,7 @@ "icons/dev/icon.ico" ], "active": true, + "category": "DeveloperTool", "targets": ["deb", "rpm", "dmg", "nsis", "app"], "externalBin": ["sidecars/opencode-cli"], "linux": { From ad7ae7353fd5aeb0800120b60667e1b84edd8e98 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 22:51:18 -0400 Subject: [PATCH 041/426] refactor(core): derive all schema.ts leaves' .zod via effect-zod walker (#23754) --- packages/opencode/specs/effect/schema.md | 16 ++++++++-------- packages/opencode/src/control-plane/schema.ts | 5 ++--- packages/opencode/src/permission/schema.ts | 5 ++--- packages/opencode/src/project/schema.ts | 4 ++-- packages/opencode/src/pty/schema.ts | 5 ++--- packages/opencode/src/question/schema.ts | 5 ++--- packages/opencode/src/session/schema.ts | 9 ++++----- packages/opencode/src/sync/schema.ts | 5 ++--- packages/opencode/src/tool/schema.ts | 5 ++--- 9 files changed, 26 insertions(+), 33 deletions(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 3ed0b825d..9ff6859ce 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -159,15 +159,15 @@ Schema at source. These are the highest-priority next targets. Each is a small, self-contained schema module with a clear domain. -- [ ] `src/control-plane/schema.ts` -- [ ] `src/permission/schema.ts` -- [ ] `src/project/schema.ts` +- [x] `src/control-plane/schema.ts` +- [x] `src/permission/schema.ts` +- [x] `src/project/schema.ts` - [x] `src/provider/schema.ts` -- [ ] `src/pty/schema.ts` -- [ ] `src/question/schema.ts` -- [ ] `src/session/schema.ts` -- [ ] `src/sync/schema.ts` -- [ ] `src/tool/schema.ts` +- [x] `src/pty/schema.ts` +- [x] `src/question/schema.ts` +- [x] `src/session/schema.ts` +- [x] `src/sync/schema.ts` +- [x] `src/tool/schema.ts` ### Session domain diff --git a/packages/opencode/src/control-plane/schema.ts b/packages/opencode/src/control-plane/schema.ts index 4c7ced010..5a0850a24 100644 --- a/packages/opencode/src/control-plane/schema.ts +++ b/packages/opencode/src/control-plane/schema.ts @@ -1,8 +1,7 @@ import { Schema } from "effect" -import z from "zod" import { Identifier } from "@/id/id" -import { ZodOverride } from "@/util/effect-zod" +import { zod, ZodOverride } from "@/util/effect-zod" import { withStatics } from "@/util/schema" const workspaceIdSchema = Schema.String.annotate({ [ZodOverride]: Identifier.schema("workspace") }).pipe( @@ -14,6 +13,6 @@ export type WorkspaceID = typeof workspaceIdSchema.Type export const WorkspaceID = workspaceIdSchema.pipe( withStatics((schema: typeof workspaceIdSchema) => ({ ascending: (id?: string) => schema.make(Identifier.ascending("workspace", id)), - zod: Identifier.schema("workspace").pipe(z.custom()), + zod: zod(schema), })), ) diff --git a/packages/opencode/src/permission/schema.ts b/packages/opencode/src/permission/schema.ts index 6ac9389a5..4eddc6a47 100644 --- a/packages/opencode/src/permission/schema.ts +++ b/packages/opencode/src/permission/schema.ts @@ -1,8 +1,7 @@ import { Schema } from "effect" -import z from "zod" import { Identifier } from "@/id/id" -import { ZodOverride } from "@/util/effect-zod" +import { zod, ZodOverride } from "@/util/effect-zod" import { Newtype } from "@/util/schema" export class PermissionID extends Newtype()( @@ -13,5 +12,5 @@ export class PermissionID extends Newtype()( return this.make(Identifier.ascending("permission", id)) } - static readonly zod = Identifier.schema("permission") as unknown as z.ZodType + static readonly zod = zod(this) } diff --git a/packages/opencode/src/project/schema.ts b/packages/opencode/src/project/schema.ts index d10c82e2c..7708b8de1 100644 --- a/packages/opencode/src/project/schema.ts +++ b/packages/opencode/src/project/schema.ts @@ -1,6 +1,6 @@ import { Schema } from "effect" -import z from "zod" +import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" const projectIdSchema = Schema.String.pipe(Schema.brand("ProjectID")) @@ -10,6 +10,6 @@ export type ProjectID = typeof projectIdSchema.Type export const ProjectID = projectIdSchema.pipe( withStatics((schema: typeof projectIdSchema) => ({ global: schema.make("global"), - zod: z.string().pipe(z.custom()), + zod: zod(schema), })), ) diff --git a/packages/opencode/src/pty/schema.ts b/packages/opencode/src/pty/schema.ts index 0758fe820..6b4d779f2 100644 --- a/packages/opencode/src/pty/schema.ts +++ b/packages/opencode/src/pty/schema.ts @@ -1,8 +1,7 @@ import { Schema } from "effect" -import z from "zod" import { Identifier } from "@/id/id" -import { ZodOverride } from "@/util/effect-zod" +import { zod, ZodOverride } from "@/util/effect-zod" import { withStatics } from "@/util/schema" const ptyIdSchema = Schema.String.annotate({ [ZodOverride]: Identifier.schema("pty") }).pipe(Schema.brand("PtyID")) @@ -12,6 +11,6 @@ export type PtyID = typeof ptyIdSchema.Type export const PtyID = ptyIdSchema.pipe( withStatics((schema: typeof ptyIdSchema) => ({ ascending: (id?: string) => schema.make(Identifier.ascending("pty", id)), - zod: Identifier.schema("pty").pipe(z.custom()), + zod: zod(schema), })), ) diff --git a/packages/opencode/src/question/schema.ts b/packages/opencode/src/question/schema.ts index 41186161d..f7a0e096a 100644 --- a/packages/opencode/src/question/schema.ts +++ b/packages/opencode/src/question/schema.ts @@ -1,8 +1,7 @@ import { Schema } from "effect" -import z from "zod" import { Identifier } from "@/id/id" -import { ZodOverride } from "@/util/effect-zod" +import { zod, ZodOverride } from "@/util/effect-zod" import { Newtype } from "@/util/schema" export class QuestionID extends Newtype()( @@ -13,5 +12,5 @@ export class QuestionID extends Newtype()( return this.make(Identifier.ascending("question", id)) } - static readonly zod = Identifier.schema("question") as unknown as z.ZodType + static readonly zod = zod(this) } diff --git a/packages/opencode/src/session/schema.ts b/packages/opencode/src/session/schema.ts index efed280c9..487cbcd34 100644 --- a/packages/opencode/src/session/schema.ts +++ b/packages/opencode/src/session/schema.ts @@ -1,15 +1,14 @@ import { Schema } from "effect" -import z from "zod" import { Identifier } from "@/id/id" -import { ZodOverride } from "@/util/effect-zod" +import { zod, ZodOverride } from "@/util/effect-zod" import { withStatics } from "@/util/schema" export const SessionID = Schema.String.annotate({ [ZodOverride]: Identifier.schema("session") }).pipe( Schema.brand("SessionID"), withStatics((s) => ({ descending: (id?: string) => s.make(Identifier.descending("session", id)), - zod: Identifier.schema("session").pipe(z.custom>()), + zod: zod(s), })), ) @@ -19,7 +18,7 @@ export const MessageID = Schema.String.annotate({ [ZodOverride]: Identifier.sche Schema.brand("MessageID"), withStatics((s) => ({ ascending: (id?: string) => s.make(Identifier.ascending("message", id)), - zod: Identifier.schema("message").pipe(z.custom>()), + zod: zod(s), })), ) @@ -29,7 +28,7 @@ export const PartID = Schema.String.annotate({ [ZodOverride]: Identifier.schema( Schema.brand("PartID"), withStatics((s) => ({ ascending: (id?: string) => s.make(Identifier.ascending("part", id)), - zod: Identifier.schema("part").pipe(z.custom>()), + zod: zod(s), })), ) diff --git a/packages/opencode/src/sync/schema.ts b/packages/opencode/src/sync/schema.ts index 37cdbd718..e714b86ae 100644 --- a/packages/opencode/src/sync/schema.ts +++ b/packages/opencode/src/sync/schema.ts @@ -1,14 +1,13 @@ import { Schema } from "effect" -import z from "zod" import { Identifier } from "@/id/id" -import { ZodOverride } from "@/util/effect-zod" +import { zod, ZodOverride } from "@/util/effect-zod" import { withStatics } from "@/util/schema" export const EventID = Schema.String.annotate({ [ZodOverride]: Identifier.schema("event") }).pipe( Schema.brand("EventID"), withStatics((s) => ({ ascending: (id?: string) => s.make(Identifier.ascending("event", id)), - zod: Identifier.schema("event").pipe(z.custom>()), + zod: zod(s), })), ) diff --git a/packages/opencode/src/tool/schema.ts b/packages/opencode/src/tool/schema.ts index ac41fd160..9ce7bece2 100644 --- a/packages/opencode/src/tool/schema.ts +++ b/packages/opencode/src/tool/schema.ts @@ -1,8 +1,7 @@ import { Schema } from "effect" -import z from "zod" import { Identifier } from "@/id/id" -import { ZodOverride } from "@/util/effect-zod" +import { zod, ZodOverride } from "@/util/effect-zod" import { withStatics } from "@/util/schema" const toolIdSchema = Schema.String.annotate({ [ZodOverride]: Identifier.schema("tool") }).pipe(Schema.brand("ToolID")) @@ -12,6 +11,6 @@ export type ToolID = typeof toolIdSchema.Type export const ToolID = toolIdSchema.pipe( withStatics((schema: typeof toolIdSchema) => ({ ascending: (id?: string) => schema.make(Identifier.ascending("tool", id)), - zod: Identifier.schema("tool").pipe(z.custom()), + zod: zod(schema), })), ) From 628102ad04f8acfadd93e112ca6592e2f7a3d697 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 21 Apr 2026 23:13:42 -0400 Subject: [PATCH 042/426] zen: handle alibaba format --- .../console/app/src/routes/zen/util/provider/anthropic.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/provider/anthropic.ts b/packages/console/app/src/routes/zen/util/provider/anthropic.ts index 0f6f11da7..de49cddc1 100644 --- a/packages/console/app/src/routes/zen/util/provider/anthropic.ts +++ b/packages/console/app/src/routes/zen/util/provider/anthropic.ts @@ -148,11 +148,13 @@ export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) => return { parse: (chunk: string) => { const data = chunk.split("\n")[1] - if (!data.startsWith("data: ")) return + // Claude models start with "data: {" + // Alibaba models start with "data:{" + if (!data.startsWith("data:")) return let json try { - json = JSON.parse(data.slice(6)) + json = JSON.parse(data.replace(/^data:\s*/, "")) } catch { return } From fa623964a262958f1225afef1e4b286b682ea19f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 23:17:23 -0400 Subject: [PATCH 043/426] refactor(core): migrate MessageV2 part leaves + ToolPart to Effect Schema (#23756) --- .../src/server/routes/instance/session.ts | 6 +- packages/opencode/src/session/message-v2.ts | 509 +++++++++++------- packages/opencode/src/session/prompt.ts | 72 +-- 3 files changed, 336 insertions(+), 251 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/session.ts b/packages/opencode/src/server/routes/instance/session.ts index a46c2f3bf..adafb8f36 100644 --- a/packages/opencode/src/server/routes/instance/session.ts +++ b/packages/opencode/src/server/routes/instance/session.ts @@ -882,7 +882,9 @@ export const SessionRoutes = lazy(() => const msg = await runRequest( "SessionRoutes.prompt", c, - SessionPrompt.Service.use((svc) => svc.prompt({ ...body, sessionID })), + SessionPrompt.Service.use((svc) => + svc.prompt({ ...body, sessionID } as unknown as SessionPrompt.PromptInput), + ), ) void stream.write(JSON.stringify(msg)) }) @@ -915,7 +917,7 @@ export const SessionRoutes = lazy(() => void runRequest( "SessionRoutes.prompt_async", c, - SessionPrompt.Service.use((svc) => svc.prompt({ ...body, sessionID })), + SessionPrompt.Service.use((svc) => svc.prompt({ ...body, sessionID } as unknown as SessionPrompt.PromptInput)), ).catch((err) => { log.error("prompt_async failed", { sessionID, error: err }) void Bus.publish(Session.Event.Error, { diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 04cb15ef8..1a12b51eb 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -86,192 +86,207 @@ const _Format = Schema.Union([OutputFormatText, OutputFormatJsonSchema]).annotat export const Format = Object.assign(_Format, { zod: zod(_Format) }) export type OutputFormat = Schema.Schema.Type -const PartBase = z.object({ - id: PartID.zod, - sessionID: SessionID.zod, - messageID: MessageID.zod, -}) +const partBase = { + id: PartID, + sessionID: SessionID, + messageID: MessageID, +} -export const SnapshotPart = PartBase.extend({ - type: z.literal("snapshot"), - snapshot: z.string(), -}).meta({ - ref: "SnapshotPart", +export const SnapshotPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("snapshot"), + snapshot: Schema.String, }) -export type SnapshotPart = z.infer + .annotate({ identifier: "SnapshotPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type SnapshotPart = Types.DeepMutable> -export const PatchPart = PartBase.extend({ - type: z.literal("patch"), - hash: z.string(), - files: z.string().array(), -}).meta({ - ref: "PatchPart", +export const PatchPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("patch"), + hash: Schema.String, + files: Schema.Array(Schema.String), }) -export type PatchPart = z.infer + .annotate({ identifier: "PatchPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type PatchPart = Types.DeepMutable> -export const TextPart = PartBase.extend({ - type: z.literal("text"), - text: z.string(), - synthetic: z.boolean().optional(), - ignored: z.boolean().optional(), - time: z - .object({ - start: z.number(), - end: z.number().optional(), - }) - .optional(), - metadata: z.record(z.string(), z.any()).optional(), -}).meta({ - ref: "TextPart", -}) -export type TextPart = z.infer - -export const ReasoningPart = PartBase.extend({ - type: z.literal("reasoning"), - text: z.string(), - metadata: z.record(z.string(), z.any()).optional(), - time: z.object({ - start: z.number(), - end: z.number().optional(), - }), -}).meta({ - ref: "ReasoningPart", -}) -export type ReasoningPart = z.infer - -const FilePartSourceBase = z.object({ - text: z - .object({ - value: z.string(), - start: z.number().int(), - end: z.number().int(), - }) - .meta({ - ref: "FilePartSourceText", +export const TextPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("text"), + text: Schema.String, + synthetic: Schema.optional(Schema.Boolean), + ignored: Schema.optional(Schema.Boolean), + time: Schema.optional( + Schema.Struct({ + start: Schema.Number, + end: Schema.optional(Schema.Number), }), + ), + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), }) + .annotate({ identifier: "TextPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type TextPart = Types.DeepMutable> -export const FileSource = FilePartSourceBase.extend({ - type: z.literal("file"), - path: z.string(), -}).meta({ - ref: "FileSource", -}) - -export const SymbolSource = FilePartSourceBase.extend({ - type: z.literal("symbol"), - path: z.string(), - range: LSP.Range.zod, - name: z.string(), - kind: z.number().int(), -}).meta({ - ref: "SymbolSource", -}) - -export const ResourceSource = FilePartSourceBase.extend({ - type: z.literal("resource"), - clientName: z.string(), - uri: z.string(), -}).meta({ - ref: "ResourceSource", -}) - -export const FilePartSource = z.discriminatedUnion("type", [FileSource, SymbolSource, ResourceSource]).meta({ - ref: "FilePartSource", -}) - -export const FilePart = PartBase.extend({ - type: z.literal("file"), - mime: z.string(), - filename: z.string().optional(), - url: z.string(), - source: FilePartSource.optional(), -}).meta({ - ref: "FilePart", -}) -export type FilePart = z.infer - -export const AgentPart = PartBase.extend({ - type: z.literal("agent"), - name: z.string(), - source: z - .object({ - value: z.string(), - start: z.number().int(), - end: z.number().int(), - }) - .optional(), -}).meta({ - ref: "AgentPart", -}) -export type AgentPart = z.infer - -export const CompactionPart = PartBase.extend({ - type: z.literal("compaction"), - auto: z.boolean(), - overflow: z.boolean().optional(), - tail_start_id: MessageID.zod.optional(), -}).meta({ - ref: "CompactionPart", -}) -export type CompactionPart = z.infer - -export const SubtaskPart = PartBase.extend({ - type: z.literal("subtask"), - prompt: z.string(), - description: z.string(), - agent: z.string(), - model: z - .object({ - providerID: ProviderID.zod, - modelID: ModelID.zod, - }) - .optional(), - command: z.string().optional(), -}).meta({ - ref: "SubtaskPart", -}) -export type SubtaskPart = z.infer - -export const RetryPart = PartBase.extend({ - type: z.literal("retry"), - attempt: z.number(), - error: APIError.Schema, - time: z.object({ - created: z.number(), +export const ReasoningPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("reasoning"), + text: Schema.String, + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), + time: Schema.Struct({ + start: Schema.Number, + end: Schema.optional(Schema.Number), }), -}).meta({ - ref: "RetryPart", }) -export type RetryPart = z.infer + .annotate({ identifier: "ReasoningPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ReasoningPart = Types.DeepMutable> -export const StepStartPart = PartBase.extend({ - type: z.literal("step-start"), - snapshot: z.string().optional(), -}).meta({ - ref: "StepStartPart", +const filePartSourceBase = { + text: Schema.Struct({ + value: Schema.String, + start: Schema.Number.check(Schema.isInt()), + end: Schema.Number.check(Schema.isInt()), + }).annotate({ identifier: "FilePartSourceText" }), +} + +export const FileSource = Schema.Struct({ + ...filePartSourceBase, + type: Schema.Literal("file"), + path: Schema.String, }) -export type StepStartPart = z.infer + .annotate({ identifier: "FileSource" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) -export const StepFinishPart = PartBase.extend({ - type: z.literal("step-finish"), - reason: z.string(), - snapshot: z.string().optional(), - cost: z.number(), - tokens: z.object({ - total: z.number().optional(), - input: z.number(), - output: z.number(), - reasoning: z.number(), - cache: z.object({ - read: z.number(), - write: z.number(), +export const SymbolSource = Schema.Struct({ + ...filePartSourceBase, + type: Schema.Literal("symbol"), + path: Schema.String, + range: LSP.Range, + name: Schema.String, + kind: Schema.Number.check(Schema.isInt()), +}) + .annotate({ identifier: "SymbolSource" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) + +export const ResourceSource = Schema.Struct({ + ...filePartSourceBase, + type: Schema.Literal("resource"), + clientName: Schema.String, + uri: Schema.String, +}) + .annotate({ identifier: "ResourceSource" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) + +const _FilePartSource = Schema.Union([FileSource, SymbolSource, ResourceSource]).annotate({ + discriminator: "type", + identifier: "FilePartSource", +}) +export const FilePartSource = Object.assign(_FilePartSource, { zod: zod(_FilePartSource) }) + +export const FilePart = Schema.Struct({ + ...partBase, + type: Schema.Literal("file"), + mime: Schema.String, + filename: Schema.optional(Schema.String), + url: Schema.String, + source: Schema.optional(_FilePartSource), +}) + .annotate({ identifier: "FilePart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type FilePart = Types.DeepMutable> + +export const AgentPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("agent"), + name: Schema.String, + source: Schema.optional( + Schema.Struct({ + value: Schema.String, + start: Schema.Number.check(Schema.isInt()), + end: Schema.Number.check(Schema.isInt()), + }), + ), +}) + .annotate({ identifier: "AgentPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type AgentPart = Types.DeepMutable> + +export const CompactionPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("compaction"), + auto: Schema.Boolean, + overflow: Schema.optional(Schema.Boolean), + tail_start_id: Schema.optional(MessageID), +}) + .annotate({ identifier: "CompactionPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type CompactionPart = Types.DeepMutable> + +export const SubtaskPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("subtask"), + prompt: Schema.String, + description: Schema.String, + agent: Schema.String, + model: Schema.optional( + Schema.Struct({ + providerID: ProviderID, + modelID: ModelID, + }), + ), + command: Schema.optional(Schema.String), +}) + .annotate({ identifier: "SubtaskPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type SubtaskPart = Types.DeepMutable> + +export const RetryPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("retry"), + attempt: Schema.Number, + // APIError is still NamedError-based Zod; bridge via ZodOverride until errors migrate. + error: Schema.Any.annotate({ [ZodOverride]: APIError.Schema }), + time: Schema.Struct({ + created: Schema.Number, + }), +}) + .annotate({ identifier: "RetryPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type RetryPart = Omit>, "error"> & { + error: APIError +} + +export const StepStartPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("step-start"), + snapshot: Schema.optional(Schema.String), +}) + .annotate({ identifier: "StepStartPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type StepStartPart = Types.DeepMutable> + +export const StepFinishPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("step-finish"), + reason: Schema.String, + snapshot: Schema.optional(Schema.String), + cost: Schema.Number, + tokens: Schema.Struct({ + total: Schema.optional(Schema.Number), + input: Schema.Number, + output: Schema.Number, + reasoning: Schema.Number, + cache: Schema.Struct({ + read: Schema.Number, + write: Schema.Number, }), }), -}).meta({ - ref: "StepFinishPart", }) -export type StepFinishPart = z.infer + .annotate({ identifier: "StepFinishPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type StepFinishPart = Types.DeepMutable> export const ToolStatePending = Schema.Struct({ status: Schema.Literal("pending"), @@ -306,18 +321,11 @@ export const ToolStateCompleted = Schema.Struct({ end: Schema.Number, compacted: Schema.optional(Schema.Number), }), - // FilePart is still Zod-first this slice; bridge via ZodOverride so the - // derived Zod + JSON Schema still emit `$ref: FilePart` array items. - attachments: Schema.optional(Schema.Any.annotate({ [ZodOverride]: FilePart.array() })), + attachments: Schema.optional(Schema.Array(FilePart)), }) .annotate({ identifier: "ToolStateCompleted" }) .pipe(withStatics((s) => ({ zod: zod(s) }))) -export type ToolStateCompleted = Omit< - Types.DeepMutable>, - "attachments" -> & { - attachments?: FilePart[] -} +export type ToolStateCompleted = Types.DeepMutable> export const ToolStateError = Schema.Struct({ status: Schema.Literal("error"), @@ -346,16 +354,19 @@ export const ToolState = Object.assign(_ToolState, { }) export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError -export const ToolPart = PartBase.extend({ - type: z.literal("tool"), - callID: z.string(), - tool: z.string(), - state: ToolState.zod, - metadata: z.record(z.string(), z.any()).optional(), -}).meta({ - ref: "ToolPart", +export const ToolPart = Schema.Struct({ + ...partBase, + type: Schema.Literal("tool"), + callID: Schema.String, + tool: Schema.String, + state: _ToolState, + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), }) -export type ToolPart = z.infer + .annotate({ identifier: "ToolPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolPart = Omit>, "state"> & { + state: ToolState +} const Base = z.object({ id: MessageID.zod, @@ -388,25 +399,114 @@ export const User = Base.extend({ }) export type User = z.infer +export type Part = + | TextPart + | SubtaskPart + | ReasoningPart + | FilePart + | ToolPart + | StepStartPart + | StepFinishPart + | SnapshotPart + | PatchPart + | AgentPart + | RetryPart + | CompactionPart + +// The derived `.zod` on each leaf is typed as `z.ZodType<...>`, but the walker +// always emits a `z.ZodObject` at runtime. `z.discriminatedUnion` and +// `z.infer` both rely on the ZodObject structural type, so cast here so the +// resulting Part behaves like the pre-migration Zod union. export const Part = z .discriminatedUnion("type", [ - TextPart, - SubtaskPart, - ReasoningPart, - FilePart, - ToolPart, - StepStartPart, - StepFinishPart, - SnapshotPart, - PatchPart, - AgentPart, - RetryPart, - CompactionPart, + TextPart.zod as unknown as z.ZodObject, + SubtaskPart.zod as unknown as z.ZodObject, + ReasoningPart.zod as unknown as z.ZodObject, + FilePart.zod as unknown as z.ZodObject, + ToolPart.zod as unknown as z.ZodObject, + StepStartPart.zod as unknown as z.ZodObject, + StepFinishPart.zod as unknown as z.ZodObject, + SnapshotPart.zod as unknown as z.ZodObject, + PatchPart.zod as unknown as z.ZodObject, + AgentPart.zod as unknown as z.ZodObject, + RetryPart.zod as unknown as z.ZodObject, + CompactionPart.zod as unknown as z.ZodObject, ]) .meta({ ref: "Part", - }) -export type Part = z.infer + }) as unknown as z.ZodType + +// ── Prompt input schemas ───────────────────────────────────────────────────── +// +// Consumers of `SessionPrompt.PromptInput.parts` send part drafts without the +// ambient IDs (`messageID`, `sessionID`) that live on stored parts, and may +// omit `id` to let the server allocate one. These Schema-Struct variants +// carry that shape, and `SessionPrompt.PromptInput` just references the +// derived `.zod` (no omit/partial gymnastics needed at the call site). + +export const TextPartInput = Schema.Struct({ + id: Schema.optional(PartID), + type: Schema.Literal("text"), + text: Schema.String, + synthetic: Schema.optional(Schema.Boolean), + ignored: Schema.optional(Schema.Boolean), + time: Schema.optional( + Schema.Struct({ + start: Schema.Number, + end: Schema.optional(Schema.Number), + }), + ), + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), +}) + .annotate({ identifier: "TextPartInput" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type TextPartInput = Types.DeepMutable> + +export const FilePartInput = Schema.Struct({ + id: Schema.optional(PartID), + type: Schema.Literal("file"), + mime: Schema.String, + filename: Schema.optional(Schema.String), + url: Schema.String, + source: Schema.optional(_FilePartSource), +}) + .annotate({ identifier: "FilePartInput" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type FilePartInput = Types.DeepMutable> + +export const AgentPartInput = Schema.Struct({ + id: Schema.optional(PartID), + type: Schema.Literal("agent"), + name: Schema.String, + source: Schema.optional( + Schema.Struct({ + value: Schema.String, + start: Schema.Number.check(Schema.isInt()), + end: Schema.Number.check(Schema.isInt()), + }), + ), +}) + .annotate({ identifier: "AgentPartInput" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type AgentPartInput = Types.DeepMutable> + +export const SubtaskPartInput = Schema.Struct({ + id: Schema.optional(PartID), + type: Schema.Literal("subtask"), + prompt: Schema.String, + description: Schema.String, + agent: Schema.String, + model: Schema.optional( + Schema.Struct({ + providerID: ProviderID, + modelID: ModelID, + }), + ), + command: Schema.optional(Schema.String), +}) + .annotate({ identifier: "SubtaskPartInput" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type SubtaskPartInput = Types.DeepMutable> export const Assistant = Base.extend({ role: z.literal("assistant"), @@ -517,7 +617,10 @@ export const WithParts = z.object({ info: Info, parts: z.array(Part), }) -export type WithParts = z.infer +export type WithParts = { + info: Info + parts: Part[] +} const Cursor = z.object({ id: MessageID.zod, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 6dcec0459..9d50db4af 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1721,50 +1721,25 @@ export const PromptInput = z.object({ variant: z.string().optional(), parts: z.array( z.discriminatedUnion("type", [ - MessageV2.TextPart.omit({ - messageID: true, - sessionID: true, - }) - .partial({ - id: true, - }) - .meta({ - ref: "TextPartInput", - }), - MessageV2.FilePart.omit({ - messageID: true, - sessionID: true, - }) - .partial({ - id: true, - }) - .meta({ - ref: "FilePartInput", - }), - MessageV2.AgentPart.omit({ - messageID: true, - sessionID: true, - }) - .partial({ - id: true, - }) - .meta({ - ref: "AgentPartInput", - }), - MessageV2.SubtaskPart.omit({ - messageID: true, - sessionID: true, - }) - .partial({ - id: true, - }) - .meta({ - ref: "SubtaskPartInput", - }), + MessageV2.TextPartInput.zod as unknown as z.ZodObject, + MessageV2.FilePartInput.zod as unknown as z.ZodObject, + MessageV2.AgentPartInput.zod as unknown as z.ZodObject, + MessageV2.SubtaskPartInput.zod as unknown as z.ZodObject, ]), ), }) -export type PromptInput = z.infer +// `z.discriminatedUnion` erases the discriminated members' shapes back to +// `{}` because the derived `.zod` on each input is typed as an opaque +// `z.ZodType`. Restore the precise `parts` type from the exported Schema +// input types so callers see a proper tagged union. +type PartInputUnion = + | MessageV2.TextPartInput + | MessageV2.FilePartInput + | MessageV2.AgentPartInput + | MessageV2.SubtaskPartInput +export type PromptInput = Omit, "parts"> & { + parts: PartInputUnion[] +} export const LoopInput = z.object({ sessionID: SessionID.zod, @@ -1792,14 +1767,19 @@ export const CommandInput = z.object({ arguments: z.string(), command: z.string(), variant: z.string().optional(), + // Inlined (no `.meta({ ref })`) to keep the original SDK output — the + // PromptInput call site below references FilePartInput by ref via the + // Schema export in message-v2.ts. parts: z .array( z.discriminatedUnion("type", [ - MessageV2.FilePart.omit({ - messageID: true, - sessionID: true, - }).partial({ - id: true, + z.object({ + id: PartID.zod.optional(), + type: z.literal("file"), + mime: z.string(), + filename: z.string().optional(), + url: z.string(), + source: MessageV2.FilePartSource.zod.optional(), }), ]), ) From 1a76799fd876d856893fdb73197545315bc06b2a Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 22 Apr 2026 03:18:25 +0000 Subject: [PATCH 044/426] chore: generate --- .../opencode/src/server/routes/instance/session.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/session.ts b/packages/opencode/src/server/routes/instance/session.ts index adafb8f36..5d1f86931 100644 --- a/packages/opencode/src/server/routes/instance/session.ts +++ b/packages/opencode/src/server/routes/instance/session.ts @@ -882,9 +882,9 @@ export const SessionRoutes = lazy(() => const msg = await runRequest( "SessionRoutes.prompt", c, - SessionPrompt.Service.use((svc) => - svc.prompt({ ...body, sessionID } as unknown as SessionPrompt.PromptInput), - ), + SessionPrompt.Service.use((svc) => + svc.prompt({ ...body, sessionID } as unknown as SessionPrompt.PromptInput), + ), ) void stream.write(JSON.stringify(msg)) }) @@ -917,7 +917,9 @@ export const SessionRoutes = lazy(() => void runRequest( "SessionRoutes.prompt_async", c, - SessionPrompt.Service.use((svc) => svc.prompt({ ...body, sessionID } as unknown as SessionPrompt.PromptInput)), + SessionPrompt.Service.use((svc) => + svc.prompt({ ...body, sessionID } as unknown as SessionPrompt.PromptInput), + ), ).catch((err) => { log.error("prompt_async failed", { sessionID, error: err }) void Bus.publish(Session.Event.Error, { From e89543811ca02067732b9ae6637bc1c1572dc7c1 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 23:26:12 -0400 Subject: [PATCH 045/426] refactor(core): migrate MessageV2 message DTOs (User/Assistant/Part/Info/WithParts) to Effect Schema (#23757) --- packages/opencode/src/cli/cmd/import.ts | 4 +- .../src/server/routes/instance/session.ts | 20 +- packages/opencode/src/session/message-v2.ts | 210 ++++++++++-------- packages/opencode/src/session/prompt.ts | 4 +- packages/opencode/src/session/session.ts | 2 +- .../test/session/structured-output.test.ts | 8 +- 6 files changed, 132 insertions(+), 116 deletions(-) diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index 8da254f15..309ec6d95 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -168,7 +168,7 @@ export const ImportCommand = cmd({ ) for (const msg of exportData.messages) { - const msgInfo = MessageV2.Info.parse(msg.info) + const msgInfo = MessageV2.Info.zod.parse(msg.info) const { id, sessionID: _, ...msgData } = msgInfo Database.use((db) => db @@ -184,7 +184,7 @@ export const ImportCommand = cmd({ ) for (const part of msg.parts) { - const partInfo = MessageV2.Part.parse(part) + const partInfo = MessageV2.Part.zod.parse(part) const { id: partId, sessionID: _s, messageID, ...partData } = partInfo Database.use((db) => db diff --git a/packages/opencode/src/server/routes/instance/session.ts b/packages/opencode/src/server/routes/instance/session.ts index 5d1f86931..8d0302426 100644 --- a/packages/opencode/src/server/routes/instance/session.ts +++ b/packages/opencode/src/server/routes/instance/session.ts @@ -611,7 +611,7 @@ export const SessionRoutes = lazy(() => description: "List of messages", content: { "application/json": { - schema: resolver(MessageV2.WithParts.array()), + schema: resolver(MessageV2.WithParts.zod.array()), }, }, }, @@ -701,8 +701,8 @@ export const SessionRoutes = lazy(() => "application/json": { schema: resolver( z.object({ - info: MessageV2.Info, - parts: MessageV2.Part.array(), + info: MessageV2.Info.zod, + parts: MessageV2.Part.zod.array(), }), ), }, @@ -813,7 +813,7 @@ export const SessionRoutes = lazy(() => description: "Successfully updated part", content: { "application/json": { - schema: resolver(MessageV2.Part), + schema: resolver(MessageV2.Part.zod), }, }, }, @@ -828,7 +828,7 @@ export const SessionRoutes = lazy(() => partID: PartID.zod, }), ), - validator("json", MessageV2.Part), + validator("json", MessageV2.Part.zod), async (c) => { const params = c.req.valid("param") const body = c.req.valid("json") @@ -856,8 +856,8 @@ export const SessionRoutes = lazy(() => "application/json": { schema: resolver( z.object({ - info: MessageV2.Assistant, - parts: MessageV2.Part.array(), + info: MessageV2.Assistant.zod, + parts: MessageV2.Part.zod.array(), }), ), }, @@ -944,8 +944,8 @@ export const SessionRoutes = lazy(() => "application/json": { schema: resolver( z.object({ - info: MessageV2.Assistant, - parts: MessageV2.Part.array(), + info: MessageV2.Assistant.zod, + parts: MessageV2.Part.zod.array(), }), ), }, @@ -980,7 +980,7 @@ export const SessionRoutes = lazy(() => description: "Created message", content: { "application/json": { - schema: resolver(MessageV2.WithParts), + schema: resolver(MessageV2.WithParts.zod), }, }, }, diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 1a12b51eb..f1cb6db21 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -368,37 +368,68 @@ export type ToolPart = Omit + .annotate({ identifier: "UserMessage" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type User = Types.DeepMutable> +const _Part = Schema.Union([ + TextPart, + SubtaskPart, + ReasoningPart, + FilePart, + ToolPart, + StepStartPart, + StepFinishPart, + SnapshotPart, + PatchPart, + AgentPart, + RetryPart, + CompactionPart, +]).annotate({ discriminator: "type", identifier: "Part" }) +export const Part = Object.assign(_Part, { + zod: zod(_Part) as unknown as z.ZodType< + | TextPart + | SubtaskPart + | ReasoningPart + | FilePart + | ToolPart + | StepStartPart + | StepFinishPart + | SnapshotPart + | PatchPart + | AgentPart + | RetryPart + | CompactionPart + >, +}) export type Part = | TextPart | SubtaskPart @@ -413,28 +444,19 @@ export type Part = | RetryPart | CompactionPart -// The derived `.zod` on each leaf is typed as `z.ZodType<...>`, but the walker -// always emits a `z.ZodObject` at runtime. `z.discriminatedUnion` and -// `z.infer` both rely on the ZodObject structural type, so cast here so the -// resulting Part behaves like the pre-migration Zod union. -export const Part = z - .discriminatedUnion("type", [ - TextPart.zod as unknown as z.ZodObject, - SubtaskPart.zod as unknown as z.ZodObject, - ReasoningPart.zod as unknown as z.ZodObject, - FilePart.zod as unknown as z.ZodObject, - ToolPart.zod as unknown as z.ZodObject, - StepStartPart.zod as unknown as z.ZodObject, - StepFinishPart.zod as unknown as z.ZodObject, - SnapshotPart.zod as unknown as z.ZodObject, - PatchPart.zod as unknown as z.ZodObject, - AgentPart.zod as unknown as z.ZodObject, - RetryPart.zod as unknown as z.ZodObject, - CompactionPart.zod as unknown as z.ZodObject, - ]) - .meta({ - ref: "Part", - }) as unknown as z.ZodType +// Errors are still NamedError-based Zod; bridge via ZodOverride so the derived +// Zod + JSON Schema emit the original discriminatedUnion shape. Migrating the +// error classes to Schema.TaggedErrorClass is a separate slice. +const AssistantErrorZod = z.discriminatedUnion("name", [ + AuthError.Schema, + NamedError.Unknown.Schema, + OutputLengthError.Schema, + AbortedError.Schema, + StructuredOutputError.Schema, + ContextOverflowError.Schema, + APIError.Schema, +]) +type AssistantError = z.infer // ── Prompt input schemas ───────────────────────────────────────────────────── // @@ -508,59 +530,53 @@ export const SubtaskPartInput = Schema.Struct({ .pipe(withStatics((s) => ({ zod: zod(s) }))) export type SubtaskPartInput = Types.DeepMutable> -export const Assistant = Base.extend({ - role: z.literal("assistant"), - time: z.object({ - created: z.number(), - completed: z.number().optional(), +export const Assistant = Schema.Struct({ + ...messageBase, + role: Schema.Literal("assistant"), + time: Schema.Struct({ + created: Schema.Number, + completed: Schema.optional(Schema.Number), }), - error: z - .discriminatedUnion("name", [ - AuthError.Schema, - NamedError.Unknown.Schema, - OutputLengthError.Schema, - AbortedError.Schema, - StructuredOutputError.Schema, - ContextOverflowError.Schema, - APIError.Schema, - ]) - .optional(), - parentID: MessageID.zod, - modelID: ModelID.zod, - providerID: ProviderID.zod, + error: Schema.optional(Schema.Any.annotate({ [ZodOverride]: AssistantErrorZod })), + parentID: MessageID, + modelID: ModelID, + providerID: ProviderID, /** * @deprecated */ - mode: z.string(), - agent: z.string(), - path: z.object({ - cwd: z.string(), - root: z.string(), + mode: Schema.String, + agent: Schema.String, + path: Schema.Struct({ + cwd: Schema.String, + root: Schema.String, }), - summary: z.boolean().optional(), - cost: z.number(), - tokens: z.object({ - total: z.number().optional(), - input: z.number(), - output: z.number(), - reasoning: z.number(), - cache: z.object({ - read: z.number(), - write: z.number(), + summary: Schema.optional(Schema.Boolean), + cost: Schema.Number, + tokens: Schema.Struct({ + total: Schema.optional(Schema.Number), + input: Schema.Number, + output: Schema.Number, + reasoning: Schema.Number, + cache: Schema.Struct({ + read: Schema.Number, + write: Schema.Number, }), }), - structured: z.any().optional(), - variant: z.string().optional(), - finish: z.string().optional(), -}).meta({ - ref: "AssistantMessage", + structured: Schema.optional(Schema.Any), + variant: Schema.optional(Schema.String), + finish: Schema.optional(Schema.String), }) -export type Assistant = z.infer + .annotate({ identifier: "AssistantMessage" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Assistant = Omit>, "error"> & { + error?: AssistantError +} -export const Info = z.discriminatedUnion("role", [User, Assistant]).meta({ - ref: "Message", +const _Info = Schema.Union([User, Assistant]).annotate({ discriminator: "role", identifier: "Message" }) +export const Info = Object.assign(_Info, { + zod: zod(_Info) as unknown as z.ZodType, }) -export type Info = z.infer +export type Info = User | Assistant export const Event = { Updated: SyncEvent.define({ @@ -569,7 +585,7 @@ export const Event = { aggregate: "sessionID", schema: z.object({ sessionID: SessionID.zod, - info: Info, + info: Info.zod, }), }), Removed: SyncEvent.define({ @@ -587,7 +603,7 @@ export const Event = { aggregate: "sessionID", schema: z.object({ sessionID: SessionID.zod, - part: Part, + part: Part.zod, time: z.number(), }), }), @@ -613,10 +629,10 @@ export const Event = { }), } -export const WithParts = z.object({ - info: Info, - parts: z.array(Part), -}) +export const WithParts = Schema.Struct({ + info: _Info, + parts: Schema.Array(_Part), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) export type WithParts = { info: Info parts: Part[] diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 9d50db4af..508c72cc8 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1243,7 +1243,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the { message: info, parts }, ) - const parsed = MessageV2.Info.safeParse(info) + const parsed = MessageV2.Info.zod.safeParse(info) if (!parsed.success) { log.error("invalid user message before save", { sessionID: input.sessionID, @@ -1254,7 +1254,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the }) } parts.forEach((part, index) => { - const p = MessageV2.Part.safeParse(part) + const p = MessageV2.Part.zod.safeParse(part) if (p.success) return log.error("invalid user part before save", { sessionID: input.sessionID, diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 6e9fb5c5d..a7607798b 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -247,7 +247,7 @@ export const Event = { z.object({ sessionID: SessionID.zod.optional(), // z.lazy defers access to break circular dep: session → message-v2 → provider → plugin → session - error: z.lazy(() => MessageV2.Assistant.shape.error), + error: z.lazy(() => (MessageV2.Assistant.zod as unknown as z.ZodObject).shape.error), }), ), } diff --git a/packages/opencode/test/session/structured-output.test.ts b/packages/opencode/test/session/structured-output.test.ts index a91446bf4..c734a182a 100644 --- a/packages/opencode/test/session/structured-output.test.ts +++ b/packages/opencode/test/session/structured-output.test.ts @@ -95,7 +95,7 @@ describe("structured-output.StructuredOutputError", () => { describe("structured-output.UserMessage", () => { test("user message accepts outputFormat", () => { - const result = MessageV2.User.safeParse({ + const result = MessageV2.User.zod.safeParse({ id: MessageID.ascending(), sessionID: SessionID.descending(), role: "user", @@ -111,7 +111,7 @@ describe("structured-output.UserMessage", () => { }) test("user message works without outputFormat (optional)", () => { - const result = MessageV2.User.safeParse({ + const result = MessageV2.User.zod.safeParse({ id: MessageID.ascending(), sessionID: SessionID.descending(), role: "user", @@ -140,7 +140,7 @@ describe("structured-output.AssistantMessage", () => { } test("assistant message accepts structured", () => { - const result = MessageV2.Assistant.safeParse({ + const result = MessageV2.Assistant.zod.safeParse({ ...baseAssistantMessage, structured: { company: "Anthropic", founded: 2021 }, }) @@ -151,7 +151,7 @@ describe("structured-output.AssistantMessage", () => { }) test("assistant message works without structured_output (optional)", () => { - const result = MessageV2.Assistant.safeParse(baseAssistantMessage) + const result = MessageV2.Assistant.zod.safeParse(baseAssistantMessage) expect(result.success).toBe(true) }) }) From 1593c3ed16369001f24252d0091092da8db26bf3 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 23:28:33 -0400 Subject: [PATCH 046/426] refactor(core): migrate MessageV2 internal Cursor to Effect Schema (#23763) --- packages/opencode/src/session/message-v2.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index f1cb6db21..aceecd9b8 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -638,18 +638,20 @@ export type WithParts = { parts: Part[] } -const Cursor = z.object({ - id: MessageID.zod, - time: z.number(), +const Cursor = Schema.Struct({ + id: MessageID, + time: Schema.Number, }) -type Cursor = z.infer +type Cursor = typeof Cursor.Type + +const decodeCursor = Schema.decodeUnknownSync(Cursor) export const cursor = { encode(input: Cursor) { return Buffer.from(JSON.stringify(input)).toString("base64url") }, decode(input: string) { - return Cursor.parse(JSON.parse(Buffer.from(input, "base64url").toString("utf8"))) + return decodeCursor(JSON.parse(Buffer.from(input, "base64url").toString("utf8"))) }, } From ed802fd121c46dd4efc3b87e7bf4865b630ccb21 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 21 Apr 2026 23:40:32 -0400 Subject: [PATCH 047/426] refactor(core): migrate MessageV2 errors to Schema-backed named errors (#23764) --- packages/opencode/src/cli/cmd/github.ts | 6 +- packages/opencode/src/session/message-v2.ts | 54 ++++++++--------- .../opencode/src/util/named-schema-error.ts | 59 +++++++++++++++++++ 3 files changed, 86 insertions(+), 33 deletions(-) create mode 100644 packages/opencode/src/util/named-schema-error.ts diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index ed1ca2124..fe8e233dd 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -985,7 +985,8 @@ export const GithubRunCommand = cmd({ const err = result.info.error console.error("Agent error:", err) if (err.name === "ContextOverflowError") throw new Error(formatPromptTooLargeError(files)) - throw new Error(`${err.name}: ${err.data?.message || ""}`) + const message = "message" in err.data ? err.data.message : "" + throw new Error(`${err.name}: ${message}`) } const text = extractResponseText(result.parts) @@ -1014,7 +1015,8 @@ export const GithubRunCommand = cmd({ const err = summary.info.error console.error("Summary agent error:", err) if (err.name === "ContextOverflowError") throw new Error(formatPromptTooLargeError(files)) - throw new Error(`${err.name}: ${err.data?.message || ""}`) + const message = "message" in err.data ? err.data.message : "" + throw new Error(`${err.name}: ${message}`) } const summaryText = extractResponseText(summary.parts) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index aceecd9b8..123f7b540 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -18,6 +18,7 @@ import { ModelID, ProviderID } from "@/provider/schema" import { Effect, Schema, Types } from "effect" import { zod, ZodOverride } from "@/util/effect-zod" import { withStatics } from "@/util/schema" +import { namedSchemaError } from "@/util/named-schema-error" import { EffectLogger } from "@/effect" /** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */ @@ -30,38 +31,29 @@ interface FetchDecompressionError extends Error { export const SYNTHETIC_ATTACHMENT_PROMPT = "Attached image(s) from tool result:" export { isMedia } -export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({})) -export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() })) -export const StructuredOutputError = NamedError.create( - "StructuredOutputError", - z.object({ - message: z.string(), - retries: z.number(), - }), -) -export const AuthError = NamedError.create( - "ProviderAuthError", - z.object({ - providerID: z.string(), - message: z.string(), - }), -) -export const APIError = NamedError.create( - "APIError", - z.object({ - message: z.string(), - statusCode: z.number().optional(), - isRetryable: z.boolean(), - responseHeaders: z.record(z.string(), z.string()).optional(), - responseBody: z.string().optional(), - metadata: z.record(z.string(), z.string()).optional(), - }), -) +export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {}) +export const AbortedError = namedSchemaError("MessageAbortedError", { message: Schema.String }) +export const StructuredOutputError = namedSchemaError("StructuredOutputError", { + message: Schema.String, + retries: Schema.Number, +}) +export const AuthError = namedSchemaError("ProviderAuthError", { + providerID: Schema.String, + message: Schema.String, +}) +export const APIError = namedSchemaError("APIError", { + message: Schema.String, + statusCode: Schema.optional(Schema.Number), + isRetryable: Schema.Boolean, + responseHeaders: Schema.optional(Schema.Record(Schema.String, Schema.String)), + responseBody: Schema.optional(Schema.String), + metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)), +}) export type APIError = z.infer -export const ContextOverflowError = NamedError.create( - "ContextOverflowError", - z.object({ message: z.string(), responseBody: z.string().optional() }), -) +export const ContextOverflowError = namedSchemaError("ContextOverflowError", { + message: Schema.String, + responseBody: Schema.optional(Schema.String), +}) export class OutputFormatText extends Schema.Class("OutputFormatText")({ type: Schema.Literal("text"), diff --git a/packages/opencode/src/util/named-schema-error.ts b/packages/opencode/src/util/named-schema-error.ts new file mode 100644 index 000000000..5fcc93cba --- /dev/null +++ b/packages/opencode/src/util/named-schema-error.ts @@ -0,0 +1,59 @@ +import { Schema } from "effect" +import z from "zod" +import { zod } from "@/util/effect-zod" + +/** + * Create a Schema-backed NamedError-shaped class. + * + * Drop-in replacement for `NamedError.create(tag, zodShape)` but backed by + * `Schema.Struct` under the hood. The wire shape emitted by the derived + * `.Schema` is still `{ name: tag, data: {...fields} }` so the generated + * OpenAPI/SDK output is byte-identical to the original NamedError schema. + * + * Preserves the existing surface: + * - static `Schema` (Zod schema of the wire shape) + * - static `isInstance(x)` + * - instance `toObject()` returning `{ name, data }` + * - `new X({ ...data }, { cause })` + */ +export function namedSchemaError(tag: Tag, fields: Fields) { + // Wire shape matches the original NamedError output so the SDK stays stable. + const dataSchema = Schema.Struct(fields) + const wire = z + .object({ + name: z.literal(tag), + data: zod(dataSchema), + }) + .meta({ ref: tag }) + + type Data = Schema.Schema.Type + + class NamedSchemaError extends Error { + static readonly Schema = wire + static readonly tag = tag + public static isInstance(input: unknown): input is NamedSchemaError { + return ( + typeof input === "object" && + input !== null && + "name" in input && + (input as { name: unknown }).name === tag + ) + } + + public override readonly name: Tag = tag + public readonly data: Data + + constructor(data: Data, options?: ErrorOptions) { + super(tag, options) + this.data = data + } + + toObject(): { name: Tag; data: Data } { + return { name: tag, data: this.data } + } + } + + Object.defineProperty(NamedSchemaError, "name", { value: tag }) + + return NamedSchemaError +} From b0455583aa8c73be47936c42c7fd96839c11e3ba Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 22 Apr 2026 03:41:42 +0000 Subject: [PATCH 048/426] chore: generate --- packages/opencode/src/util/named-schema-error.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/opencode/src/util/named-schema-error.ts b/packages/opencode/src/util/named-schema-error.ts index 5fcc93cba..e144f2f90 100644 --- a/packages/opencode/src/util/named-schema-error.ts +++ b/packages/opencode/src/util/named-schema-error.ts @@ -32,12 +32,7 @@ export function namedSchemaError Date: Wed, 22 Apr 2026 15:18:51 +1000 Subject: [PATCH 049/426] chore: bump Bun to 1.3.13 (#23791) --- bun.lock | 6 +++--- package.json | 4 ++-- packages/containers/bun-node/Dockerfile | 2 +- packages/ui/src/components/timeline-playground.stories.tsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bun.lock b/bun.lock index 77ab24240..64b32feac 100644 --- a/bun.lock +++ b/bun.lock @@ -688,7 +688,7 @@ "@tailwindcss/vite": "4.1.11", "@tsconfig/bun": "1.0.9", "@tsconfig/node22": "22.0.2", - "@types/bun": "1.3.11", + "@types/bun": "1.3.12", "@types/cross-spawn": "6.0.6", "@types/luxon": "3.7.1", "@types/node": "22.13.9", @@ -2302,7 +2302,7 @@ "@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="], - "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], + "@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="], "@types/cacache": ["@types/cacache@20.0.1", "", { "dependencies": { "@types/node": "*", "minipass": "*" } }, "sha512-QlKW3AFoFr/hvPHwFHMIVUH/ZCYeetBNou3PCmxu5LaNDvrtBlPJtIA6uhmU9JRt9oxj7IYoqoLcpxtzpPiTcw=="], @@ -2720,7 +2720,7 @@ "bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="], - "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], "bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="], diff --git a/package.json b/package.json index 06bf9c91a..f918bcd02 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "AI-powered development tool", "private": true, "type": "module", - "packageManager": "bun@1.3.11", + "packageManager": "bun@1.3.13", "scripts": { "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", "dev:desktop": "bun --cwd packages/desktop-electron dev", @@ -30,7 +30,7 @@ "@effect/opentelemetry": "4.0.0-beta.48", "@effect/platform-node": "4.0.0-beta.48", "@npmcli/arborist": "9.4.0", - "@types/bun": "1.3.11", + "@types/bun": "1.3.12", "@types/cross-spawn": "6.0.6", "@octokit/rest": "22.0.0", "@hono/zod-validator": "0.4.2", diff --git a/packages/containers/bun-node/Dockerfile b/packages/containers/bun-node/Dockerfile index 485375dd9..d6f4729bf 100644 --- a/packages/containers/bun-node/Dockerfile +++ b/packages/containers/bun-node/Dockerfile @@ -4,7 +4,7 @@ FROM ${REGISTRY}/build/base:24.04 SHELL ["/bin/bash", "-lc"] ARG NODE_VERSION=24.4.0 -ARG BUN_VERSION=1.3.11 +ARG BUN_VERSION=1.3.13 ENV BUN_INSTALL=/opt/bun ENV PATH=/opt/bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin diff --git a/packages/ui/src/components/timeline-playground.stories.tsx b/packages/ui/src/components/timeline-playground.stories.tsx index c071db303..72f573061 100644 --- a/packages/ui/src/components/timeline-playground.stories.tsx +++ b/packages/ui/src/components/timeline-playground.stories.tsx @@ -318,7 +318,7 @@ const TOOL_SAMPLES = { tool: "bash", input: { command: "bun test --filter session", description: "Run session tests" }, output: - "bun test v1.3.11\n\n✓ session-turn.test.tsx (3 tests) 45ms\n✓ message-part.test.tsx (7 tests) 120ms\n\nTest Suites: 2 passed, 2 total\nTests: 10 passed, 10 total\nTime: 0.89s", + "bun test v1.3.13\n\n✓ session-turn.test.tsx (3 tests) 45ms\n✓ message-part.test.tsx (7 tests) 120ms\n\nTest Suites: 2 passed, 2 total\nTests: 10 passed, 10 total\nTime: 0.89s", title: "Run session tests", metadata: { command: "bun test --filter session" }, }, From 06066dbb7bd05a99526f7b6a5eaa499d1cf47f78 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Wed, 22 Apr 2026 13:36:20 +0800 Subject: [PATCH 050/426] fix(app): improve icon override handling in project edit dialog (#23768) --- .../src/components/dialog-edit-project.tsx | 36 ++++++++++--------- packages/app/src/context/layout.tsx | 2 +- .../app/src/pages/layout/sidebar-items.tsx | 4 ++- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx index ea5d70065..621d56646 100644 --- a/packages/app/src/components/dialog-edit-project.tsx +++ b/packages/app/src/components/dialog-edit-project.tsx @@ -26,8 +26,8 @@ export function DialogEditProject(props: { project: LocalProject }) { const [store, setStore] = createStore({ name: defaultName(), - color: props.project.icon?.color || "pink", - iconUrl: props.project.icon?.override || "", + color: props.project.icon?.color, + iconOverride: props.project.icon?.override, startup: props.project.commands?.start ?? "", dragOver: false, iconHover: false, @@ -39,7 +39,7 @@ export function DialogEditProject(props: { project: LocalProject }) { if (!file.type.startsWith("image/")) return const reader = new FileReader() reader.onload = (e) => { - setStore("iconUrl", e.target?.result as string) + setStore("iconOverride", e.target?.result as string) setStore("iconHover", false) } reader.readAsDataURL(file) @@ -68,7 +68,7 @@ export function DialogEditProject(props: { project: LocalProject }) { } function clearIcon() { - setStore("iconUrl", "") + setStore("iconOverride", "") } const saveMutation = useMutation(() => ({ @@ -81,17 +81,17 @@ export function DialogEditProject(props: { project: LocalProject }) { projectID: props.project.id, directory: props.project.worktree, name, - icon: { color: store.color, override: store.iconUrl }, + icon: { color: store.color || "", override: store.iconOverride || "" }, commands: { start }, }) - globalSync.project.icon(props.project.worktree, store.iconUrl || undefined) + globalSync.project.icon(props.project.worktree, store.iconOverride || undefined) dialog.close() return } globalSync.project.meta(props.project.worktree, { name, - icon: { color: store.color, override: store.iconUrl || undefined }, + icon: { color: store.color || undefined, override: store.iconOverride || undefined }, commands: { start: start || undefined }, }) dialog.close() @@ -130,13 +130,13 @@ export function DialogEditProject(props: { project: LocalProject }) { classList={{ "border-text-interactive-base bg-surface-info-base/20": store.dragOver, "border-border-base hover:border-border-strong": !store.dragOver, - "overflow-hidden": !!store.iconUrl, + "overflow-hidden": !!store.iconOverride, }} onDrop={handleDrop} onDragOver={handleDragOver} onDragLeave={handleDragLeave} onClick={() => { - if (store.iconUrl && store.iconHover) { + if (store.iconOverride && store.iconHover) { clearIcon() } else { iconInput?.click() @@ -144,7 +144,7 @@ export function DialogEditProject(props: { project: LocalProject }) { }} > {language.t("dialog.project.edit.icon.alt")} @@ -165,8 +165,8 @@ export function DialogEditProject(props: { project: LocalProject }) {
@@ -174,8 +174,8 @@ export function DialogEditProject(props: { project: LocalProject }) {
@@ -198,7 +198,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
- +
@@ -215,7 +215,9 @@ export function DialogEditProject(props: { project: LocalProject }) { "bg-transparent border border-transparent hover:bg-surface-base-hover hover:border-border-weak-base": store.color !== color, }} - onClick={() => setStore("color", color)} + onClick={() => { + setStore("color", store.color === color ? undefined : color) + }} > Date: Wed, 22 Apr 2026 06:13:39 +0000 Subject: [PATCH 051/426] 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 21279a327..c09604610 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-NczRp8MPppkqP8PQfWMUWJ/Wofvf2YVy5m4i22Pi3jg=", - "aarch64-linux": "sha256-QIxGOu8Fj+sWgc9hKvm1BLiIErxEtd17SPlwZGac9sQ=", - "aarch64-darwin": "sha256-Rb9qbMM+ARn0iBCaZurwcoUBCplbMXEZwrXVKextp3I=", - "x86_64-darwin": "sha256-KVxOKkaVV7W+K4reEk14MTLgmtoqwCYDqDNXNeS6ync=" + "x86_64-linux": "sha256-AgHhYsiygxbsBo3JN4HqHXKAwh8n1qeuSCe2qqxlxW4=", + "aarch64-linux": "sha256-h2lpWRQ5EDYnjpqZXtUAp1mxKLQxJ4m8MspgSY8Ev78=", + "aarch64-darwin": "sha256-xnd91+WyeAqn06run2ajsekxJvTMiLsnqNPe/rR8VTM=", + "x86_64-darwin": "sha256-rXpz45IOjGEk73xhP9VY86eOj2CZBg2l1vzwzTIOOOQ=" } } From 8113a4360e789fb25ac16d5619e732f3152ad3df Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:03:34 +1000 Subject: [PATCH 052/426] fix: preserve BOM in text tool round-trips (#23797) --- packages/opencode/src/format/index.ts | 17 +++-- packages/opencode/src/patch/index.ts | 18 +++--- packages/opencode/src/tool/apply_patch.ts | 32 +++++++--- packages/opencode/src/tool/edit.ts | 29 ++++++--- packages/opencode/src/tool/write.ts | 15 +++-- packages/opencode/src/util/bom.ts | 31 +++++++++ packages/opencode/test/format/format.test.ts | 34 +++++++++- .../opencode/test/tool/apply_patch.test.ts | 28 +++++++++ packages/opencode/test/tool/edit.test.ts | 63 +++++++++++++++++++ packages/opencode/test/tool/write.test.ts | 48 ++++++++++++++ 10 files changed, 276 insertions(+), 39 deletions(-) create mode 100644 packages/opencode/src/util/bom.ts diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 85934ce9c..53a2c1011 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -25,7 +25,7 @@ export type Status = z.infer export interface Interface { readonly init: () => Effect.Effect readonly status: () => Effect.Effect - readonly file: (filepath: string) => Effect.Effect + readonly file: (filepath: string) => Effect.Effect } export class Service extends Context.Service()("@opencode/Format") {} @@ -70,16 +70,19 @@ export const layer = Layer.effect( } }), ) - return checks.filter((x) => x.cmd).map((x) => ({ item: x.item, cmd: x.cmd! })) + return checks + .filter((x): x is { item: Formatter.Info; cmd: string[] } => x.cmd !== false) + .map((x) => ({ item: x.item, cmd: x.cmd })) } function formatFile(filepath: string) { return Effect.gen(function* () { log.info("formatting", { file: filepath }) - const ext = path.extname(filepath) + const formatters = yield* Effect.promise(() => getFormatter(path.extname(filepath))) - for (const { item, cmd } of yield* Effect.promise(() => getFormatter(ext))) { - if (cmd === false) continue + if (!formatters.length) return false + + for (const { item, cmd } of formatters) { log.info("running", { command: cmd }) const replaced = cmd.map((x) => x.replace("$FILE", filepath)) const dir = yield* InstanceState.directory @@ -113,6 +116,8 @@ export const layer = Layer.effect( }) } } + + return true }) } @@ -188,7 +193,7 @@ export const layer = Layer.effect( const file = Effect.fn("Format.file")(function* (filepath: string) { const { formatFile } = yield* InstanceState.get(state) - yield* formatFile(filepath) + return yield* formatFile(filepath) }) return Service.of({ init, status, file }) diff --git a/packages/opencode/src/patch/index.ts b/packages/opencode/src/patch/index.ts index 19e1d7555..3662f9e90 100644 --- a/packages/opencode/src/patch/index.ts +++ b/packages/opencode/src/patch/index.ts @@ -3,6 +3,7 @@ import * as path from "path" import * as fs from "fs/promises" import { readFileSync } from "fs" import { Log } from "../util" +import * as Bom from "../util/bom" const log = Log.create({ service: "patch" }) @@ -305,18 +306,19 @@ export function maybeParseApplyPatch( interface ApplyPatchFileUpdate { unified_diff: string content: string + bom: boolean } export function deriveNewContentsFromChunks(filePath: string, chunks: UpdateFileChunk[]): ApplyPatchFileUpdate { // Read original file content - let originalContent: string + let originalContent: ReturnType try { - originalContent = readFileSync(filePath, "utf-8") + originalContent = Bom.split(readFileSync(filePath, "utf-8")) } catch (error) { throw new Error(`Failed to read file ${filePath}: ${error}`, { cause: error }) } - let originalLines = originalContent.split("\n") + let originalLines = originalContent.text.split("\n") // Drop trailing empty element for consistent line counting if (originalLines.length > 0 && originalLines[originalLines.length - 1] === "") { @@ -331,14 +333,16 @@ export function deriveNewContentsFromChunks(filePath: string, chunks: UpdateFile newLines.push("") } - const newContent = newLines.join("\n") + const next = Bom.split(newLines.join("\n")) + const newContent = next.text // Generate unified diff - const unifiedDiff = generateUnifiedDiff(originalContent, newContent) + const unifiedDiff = generateUnifiedDiff(originalContent.text, newContent) return { unified_diff: unifiedDiff, content: newContent, + bom: originalContent.bom || next.bom, } } @@ -553,13 +557,13 @@ export async function applyHunksToFiles(hunks: Hunk[]): Promise { await fs.mkdir(moveDir, { recursive: true }) } - await fs.writeFile(hunk.move_path, fileUpdate.content, "utf-8") + await fs.writeFile(hunk.move_path, Bom.join(fileUpdate.content, fileUpdate.bom), "utf-8") await fs.unlink(hunk.path) modified.push(hunk.move_path) log.info(`Moved file: ${hunk.path} -> ${hunk.move_path}`) } else { // Regular update - await fs.writeFile(hunk.path, fileUpdate.content, "utf-8") + await fs.writeFile(hunk.path, Bom.join(fileUpdate.content, fileUpdate.bom), "utf-8") modified.push(hunk.path) log.info(`Updated file: ${hunk.path}`) } diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts index 7da7dd255..e36d5a65d 100644 --- a/packages/opencode/src/tool/apply_patch.ts +++ b/packages/opencode/src/tool/apply_patch.ts @@ -14,6 +14,7 @@ import { AppFileSystem } from "@opencode-ai/shared/filesystem" import DESCRIPTION from "./apply_patch.txt" import { File } from "../file" import { Format } from "../format" +import * as Bom from "@/util/bom" const PatchParams = z.object({ patchText: z.string().describe("The full patch text that describes all changes to be made"), @@ -59,6 +60,7 @@ export const ApplyPatchTool = Tool.define( diff: string additions: number deletions: number + bom: boolean }> = [] let totalDiff = "" @@ -72,11 +74,12 @@ export const ApplyPatchTool = Tool.define( const oldContent = "" const newContent = hunk.contents.length === 0 || hunk.contents.endsWith("\n") ? hunk.contents : `${hunk.contents}\n` - const diff = trimDiff(createTwoFilesPatch(filePath, filePath, oldContent, newContent)) + const next = Bom.split(newContent) + const diff = trimDiff(createTwoFilesPatch(filePath, filePath, oldContent, next.text)) let additions = 0 let deletions = 0 - for (const change of diffLines(oldContent, newContent)) { + for (const change of diffLines(oldContent, next.text)) { if (change.added) additions += change.count || 0 if (change.removed) deletions += change.count || 0 } @@ -84,11 +87,12 @@ export const ApplyPatchTool = Tool.define( fileChanges.push({ filePath, oldContent, - newContent, + newContent: next.text, type: "add", diff, additions, deletions, + bom: next.bom, }) totalDiff += diff + "\n" @@ -104,13 +108,16 @@ export const ApplyPatchTool = Tool.define( ) } - const oldContent = yield* afs.readFileString(filePath) + const source = yield* Bom.readFile(afs, filePath) + const oldContent = source.text let newContent = oldContent + let bom = source.bom // Apply the update chunks to get new content try { const fileUpdate = Patch.deriveNewContentsFromChunks(filePath, hunk.chunks) newContent = fileUpdate.content + bom = fileUpdate.bom } catch (error) { return yield* Effect.fail(new Error(`apply_patch verification failed: ${error}`)) } @@ -136,6 +143,7 @@ export const ApplyPatchTool = Tool.define( diff, additions, deletions, + bom, }) totalDiff += diff + "\n" @@ -143,8 +151,8 @@ export const ApplyPatchTool = Tool.define( } case "delete": { - const contentToDelete = yield* afs - .readFileString(filePath) + const source = yield* Bom + .readFile(afs, filePath) .pipe( Effect.catch((error) => Effect.fail( @@ -154,6 +162,7 @@ export const ApplyPatchTool = Tool.define( ), ), ) + const contentToDelete = source.text const deleteDiff = trimDiff(createTwoFilesPatch(filePath, filePath, contentToDelete, "")) const deletions = contentToDelete.split("\n").length @@ -166,6 +175,7 @@ export const ApplyPatchTool = Tool.define( diff: deleteDiff, additions: 0, deletions, + bom: source.bom, }) totalDiff += deleteDiff + "\n" @@ -207,12 +217,12 @@ export const ApplyPatchTool = Tool.define( case "add": // Create parent directories (recursive: true is safe on existing/root dirs) - yield* afs.writeWithDirs(change.filePath, change.newContent) + yield* afs.writeWithDirs(change.filePath, Bom.join(change.newContent, change.bom)) updates.push({ file: change.filePath, event: "add" }) break case "update": - yield* afs.writeWithDirs(change.filePath, change.newContent) + yield* afs.writeWithDirs(change.filePath, Bom.join(change.newContent, change.bom)) updates.push({ file: change.filePath, event: "change" }) break @@ -220,7 +230,7 @@ export const ApplyPatchTool = Tool.define( if (change.movePath) { // Create parent directories (recursive: true is safe on existing/root dirs) - yield* afs.writeWithDirs(change.movePath!, change.newContent) + yield* afs.writeWithDirs(change.movePath!, Bom.join(change.newContent, change.bom)) yield* afs.remove(change.filePath) updates.push({ file: change.filePath, event: "unlink" }) updates.push({ file: change.movePath, event: "add" }) @@ -234,7 +244,9 @@ export const ApplyPatchTool = Tool.define( } if (edited) { - yield* format.file(edited) + if (yield* format.file(edited)) { + yield* Bom.syncFile(afs, edited, change.bom) + } yield* bus.publish(File.Event.Edited, { file: edited }) } } diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 2c6c2c130..858d14e04 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -18,6 +18,7 @@ import { Instance } from "../project/instance" import { Snapshot } from "@/snapshot" import { assertExternalDirectoryEffect } from "./external-directory" import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import * as Bom from "@/util/bom" function normalizeLineEndings(text: string): string { return text.replaceAll("\r\n", "\n") @@ -84,7 +85,11 @@ export const EditTool = Tool.define( Effect.gen(function* () { if (params.oldString === "") { const existed = yield* afs.existsSafe(filePath) - contentNew = params.newString + const source = existed ? yield* Bom.readFile(afs, filePath) : { bom: false, text: "" } + const next = Bom.split(params.newString) + const desiredBom = source.bom || next.bom + contentOld = source.text + contentNew = next.text diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew)) yield* ctx.ask({ permission: "edit", @@ -95,8 +100,10 @@ export const EditTool = Tool.define( diff, }, }) - yield* afs.writeWithDirs(filePath, params.newString) - yield* format.file(filePath) + yield* afs.writeWithDirs(filePath, Bom.join(contentNew, desiredBom)) + if (yield* format.file(filePath)) { + contentNew = yield* Bom.syncFile(afs, filePath, desiredBom) + } yield* bus.publish(File.Event.Edited, { file: filePath }) yield* bus.publish(FileWatcher.Event.Updated, { file: filePath, @@ -108,13 +115,16 @@ export const EditTool = Tool.define( const info = yield* afs.stat(filePath).pipe(Effect.catch(() => Effect.succeed(undefined))) if (!info) throw new Error(`File ${filePath} not found`) if (info.type === "Directory") throw new Error(`Path is a directory, not a file: ${filePath}`) - contentOld = yield* afs.readFileString(filePath) + const source = yield* Bom.readFile(afs, filePath) + contentOld = source.text const ending = detectLineEnding(contentOld) const old = convertToLineEnding(normalizeLineEndings(params.oldString), ending) - const next = convertToLineEnding(normalizeLineEndings(params.newString), ending) + const replacement = convertToLineEnding(normalizeLineEndings(params.newString), ending) - contentNew = replace(contentOld, old, next, params.replaceAll) + const next = Bom.split(replace(contentOld, old, replacement, params.replaceAll)) + const desiredBom = source.bom || next.bom + contentNew = next.text diff = trimDiff( createTwoFilesPatch( @@ -134,14 +144,15 @@ export const EditTool = Tool.define( }, }) - yield* afs.writeWithDirs(filePath, contentNew) - yield* format.file(filePath) + yield* afs.writeWithDirs(filePath, Bom.join(contentNew, desiredBom)) + if (yield* format.file(filePath)) { + contentNew = yield* Bom.syncFile(afs, filePath, desiredBom) + } yield* bus.publish(File.Event.Edited, { file: filePath }) yield* bus.publish(FileWatcher.Event.Updated, { file: filePath, event: "change", }) - contentNew = yield* afs.readFileString(filePath) diff = trimDiff( createTwoFilesPatch( filePath, diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 741091b21..79ed58519 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -13,6 +13,7 @@ import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Instance } from "../project/instance" import { trimDiff } from "./edit" import { assertExternalDirectoryEffect } from "./external-directory" +import * as Bom from "@/util/bom" const MAX_PROJECT_DIAGNOSTICS_FILES = 5 @@ -38,9 +39,13 @@ export const WriteTool = Tool.define( yield* assertExternalDirectoryEffect(ctx, filepath) const exists = yield* fs.existsSafe(filepath) - const contentOld = exists ? yield* fs.readFileString(filepath) : "" + const source = exists ? yield* Bom.readFile(fs, filepath) : { bom: false, text: "" } + const next = Bom.split(params.content) + const desiredBom = source.bom || next.bom + const contentOld = source.text + const contentNew = next.text - const diff = trimDiff(createTwoFilesPatch(filepath, filepath, contentOld, params.content)) + const diff = trimDiff(createTwoFilesPatch(filepath, filepath, contentOld, contentNew)) yield* ctx.ask({ permission: "edit", patterns: [path.relative(Instance.worktree, filepath)], @@ -51,8 +56,10 @@ export const WriteTool = Tool.define( }, }) - yield* fs.writeWithDirs(filepath, params.content) - yield* format.file(filepath) + yield* fs.writeWithDirs(filepath, Bom.join(contentNew, desiredBom)) + if (yield* format.file(filepath)) { + yield* Bom.syncFile(fs, filepath, desiredBom) + } yield* bus.publish(File.Event.Edited, { file: filepath }) yield* bus.publish(FileWatcher.Event.Updated, { file: filepath, diff --git a/packages/opencode/src/util/bom.ts b/packages/opencode/src/util/bom.ts new file mode 100644 index 000000000..484228f3d --- /dev/null +++ b/packages/opencode/src/util/bom.ts @@ -0,0 +1,31 @@ +import { Effect } from "effect" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" + +const BOM_CODE = 0xfeff +const BOM = String.fromCharCode(BOM_CODE) + +export function split(text: string) { + if (text.charCodeAt(0) !== BOM_CODE) return { bom: false, text } + return { bom: true, text: text.slice(1) } +} + +export function join(text: string, bom: boolean) { + const stripped = split(text).text + if (!bom) return stripped + return BOM + stripped +} + +export const readFile = Effect.fn("Bom.readFile")(function* (fs: AppFileSystem.Interface, filePath: string) { + return split(new TextDecoder("utf-8", { ignoreBOM: true }).decode(yield* fs.readFile(filePath))) +}) + +export const syncFile = Effect.fn("Bom.syncFile")(function* ( + fs: AppFileSystem.Interface, + filePath: string, + bom: boolean, +) { + const current = yield* readFile(fs, filePath) + if (current.bom === bom) return current.text + yield* fs.writeWithDirs(filePath, join(current.text, bom)) + return current.text +}) diff --git a/packages/opencode/test/format/format.test.ts b/packages/opencode/test/format/format.test.ts index 5530e195b..2f6f235aa 100644 --- a/packages/opencode/test/format/format.test.ts +++ b/packages/opencode/test/format/format.test.ts @@ -126,6 +126,24 @@ describe("Format", () => { it.live("service initializes without error", () => provideTmpdirInstance(() => Format.Service.use(() => Effect.void))) + it.live("file() returns false when no formatter runs", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const file = `${dir}/test.txt` + yield* Effect.promise(() => Bun.write(file, "x")) + + const formatted = yield* Format.Service.use((fmt) => fmt.file(file)) + expect(formatted).toBe(false) + }), + { + config: { + formatter: false, + }, + }, + ), + ) + it.live("status() initializes formatter state per directory", () => Effect.gen(function* () { const a = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status()), { @@ -219,7 +237,7 @@ describe("Format", () => { yield* Format.Service.use((fmt) => Effect.gen(function* () { yield* fmt.init() - yield* fmt.file(file) + expect(yield* fmt.file(file)).toBe(true) }), ) @@ -229,11 +247,21 @@ describe("Format", () => { config: { formatter: { first: { - command: ["sh", "-c", 'sleep 0.05; v=$(cat "$1"); printf \'%sA\' "$v" > "$1"', "sh", "$FILE"], + command: [ + "node", + "-e", + "const fs = require('fs'); const file = process.argv[1]; fs.writeFileSync(file, fs.readFileSync(file, 'utf8') + 'A')", + "$FILE", + ], extensions: [".seq"], }, second: { - command: ["sh", "-c", 'v=$(cat "$1"); printf \'%sB\' "$v" > "$1"', "sh", "$FILE"], + command: [ + "node", + "-e", + "const fs = require('fs'); const file = process.argv[1]; fs.writeFileSync(file, fs.readFileSync(file, 'utf8') + 'B')", + "$FILE", + ], extensions: [".seq"], }, }, diff --git a/packages/opencode/test/tool/apply_patch.test.ts b/packages/opencode/test/tool/apply_patch.test.ts index ebfa9a531..7ce483726 100644 --- a/packages/opencode/test/tool/apply_patch.test.ts +++ b/packages/opencode/test/tool/apply_patch.test.ts @@ -195,6 +195,34 @@ describe("tool.apply_patch freeform", () => { }) }) + test("does not invent a first-line diff for BOM files", async () => { + await using fixture = await tmpdir() + const { ctx, calls } = makeCtx() + + await Instance.provide({ + directory: fixture.path, + fn: async () => { + const bom = String.fromCharCode(0xfeff) + const target = path.join(fixture.path, "example.cs") + await fs.writeFile(target, `${bom}using System;\n\nclass Test {}\n`, "utf-8") + + const patchText = "*** Begin Patch\n*** Update File: example.cs\n@@\n class Test {}\n+class Next {}\n*** End Patch" + + await execute({ patchText }, ctx) + + expect(calls.length).toBe(1) + const shown = calls[0].metadata.files[0]?.patch ?? "" + expect(shown).not.toContain(bom) + expect(shown).not.toContain("-using System;") + expect(shown).not.toContain("+using System;") + + const content = await fs.readFile(target, "utf-8") + expect(content.charCodeAt(0)).toBe(0xfeff) + expect(content.slice(1)).toBe("using System;\n\nclass Test {}\nclass Next {}\n") + }, + }) + }) + test("inserts lines with insert-only hunk", async () => { await using fixture = await tmpdir() const { ctx } = makeCtx() diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts index b5fbc0a67..82e1b4a7f 100644 --- a/packages/opencode/test/tool/edit.test.ts +++ b/packages/opencode/test/tool/edit.test.ts @@ -96,6 +96,37 @@ describe("tool.edit", () => { }) }) + test("preserves BOM when oldString is empty on existing files", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "existing.cs") + const bom = String.fromCharCode(0xfeff) + await fs.writeFile(filepath, `${bom}using System;\n`, "utf-8") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const edit = await resolve() + const result = await Effect.runPromise( + edit.execute( + { + filePath: filepath, + oldString: "", + newString: "using Up;\n", + }, + ctx, + ), + ) + + expect(result.metadata.diff).toContain("-using System;") + expect(result.metadata.diff).toContain("+using Up;") + + const content = await fs.readFile(filepath, "utf-8") + expect(content.charCodeAt(0)).toBe(0xfeff) + expect(content.slice(1)).toBe("using Up;\n") + }, + }) + }) + test("creates new file with nested directories", async () => { await using tmp = await tmpdir() const filepath = path.join(tmp.path, "nested", "dir", "file.txt") @@ -183,6 +214,38 @@ describe("tool.edit", () => { }) }) + test("replaces the first visible line in BOM files", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "existing.cs") + const bom = String.fromCharCode(0xfeff) + await fs.writeFile(filepath, `${bom}using System;\nclass Test {}\n`, "utf-8") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const edit = await resolve() + const result = await Effect.runPromise( + edit.execute( + { + filePath: filepath, + oldString: "using System;", + newString: "using Up;", + }, + ctx, + ), + ) + + expect(result.metadata.diff).toContain("-using System;") + expect(result.metadata.diff).toContain("+using Up;") + expect(result.metadata.diff).not.toContain(bom) + + const content = await fs.readFile(filepath, "utf-8") + expect(content.charCodeAt(0)).toBe(0xfeff) + expect(content.slice(1)).toBe("using Up;\nclass Test {}\n") + }, + }) + }) + test("throws error when file does not exist", async () => { await using tmp = await tmpdir() const filepath = path.join(tmp.path, "nonexistent.txt") diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts index 50d3b5752..36131f959 100644 --- a/packages/opencode/test/tool/write.test.ts +++ b/packages/opencode/test/tool/write.test.ts @@ -114,6 +114,54 @@ describe("tool.write", () => { ), ) + it.live("preserves BOM when overwriting existing files", () => + provideTmpdirInstance((dir) => + Effect.gen(function* () { + const filepath = path.join(dir, "existing.cs") + const bom = String.fromCharCode(0xfeff) + yield* Effect.promise(() => fs.writeFile(filepath, `${bom}using System;\n`, "utf-8")) + + yield* run({ filePath: filepath, content: "using Up;\n" }) + + const content = yield* Effect.promise(() => fs.readFile(filepath, "utf-8")) + expect(content.charCodeAt(0)).toBe(0xfeff) + expect(content.slice(1)).toBe("using Up;\n") + }), + ), + ) + + it.live("restores BOM after formatter strips it", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const filepath = path.join(dir, "formatted.cs") + const bom = String.fromCharCode(0xfeff) + yield* Effect.promise(() => fs.writeFile(filepath, `${bom}using System;\n`, "utf-8")) + + yield* run({ filePath: filepath, content: "using Up;\n" }) + + const content = yield* Effect.promise(() => fs.readFile(filepath, "utf-8")) + expect(content.charCodeAt(0)).toBe(0xfeff) + expect(content.slice(1)).toBe("using Up;\n") + }), + { + config: { + formatter: { + stripbom: { + extensions: [".cs"], + command: [ + "node", + "-e", + "const fs = require('fs'); const file = process.argv[1]; let text = fs.readFileSync(file, 'utf8'); if (text.charCodeAt(0) === 0xfeff) text = text.slice(1); fs.writeFileSync(file, text, 'utf8')", + "$FILE", + ], + }, + }, + }, + }, + ), + ) + it.live("returns diff in metadata for existing files", () => provideTmpdirInstance((dir) => Effect.gen(function* () { From 894e63891483cb0d79f7ca28410dcdfa604d35bf Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 22 Apr 2026 08:06:06 +0000 Subject: [PATCH 053/426] chore: generate --- packages/opencode/src/tool/apply_patch.ts | 16 +++++++--------- packages/opencode/test/tool/apply_patch.test.ts | 3 ++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts index e36d5a65d..a4cf1e853 100644 --- a/packages/opencode/src/tool/apply_patch.ts +++ b/packages/opencode/src/tool/apply_patch.ts @@ -151,17 +151,15 @@ export const ApplyPatchTool = Tool.define( } case "delete": { - const source = yield* Bom - .readFile(afs, filePath) - .pipe( - Effect.catch((error) => - Effect.fail( - new Error( - `apply_patch verification failed: ${error instanceof Error ? error.message : String(error)}`, - ), + const source = yield* Bom.readFile(afs, filePath).pipe( + Effect.catch((error) => + Effect.fail( + new Error( + `apply_patch verification failed: ${error instanceof Error ? error.message : String(error)}`, ), ), - ) + ), + ) const contentToDelete = source.text const deleteDiff = trimDiff(createTwoFilesPatch(filePath, filePath, contentToDelete, "")) diff --git a/packages/opencode/test/tool/apply_patch.test.ts b/packages/opencode/test/tool/apply_patch.test.ts index 7ce483726..fa8843213 100644 --- a/packages/opencode/test/tool/apply_patch.test.ts +++ b/packages/opencode/test/tool/apply_patch.test.ts @@ -206,7 +206,8 @@ describe("tool.apply_patch freeform", () => { const target = path.join(fixture.path, "example.cs") await fs.writeFile(target, `${bom}using System;\n\nclass Test {}\n`, "utf-8") - const patchText = "*** Begin Patch\n*** Update File: example.cs\n@@\n class Test {}\n+class Next {}\n*** End Patch" + const patchText = + "*** Begin Patch\n*** Update File: example.cs\n@@\n class Test {}\n+class Next {}\n*** End Patch" await execute({ patchText }, ctx) From 20756e0ee45885db5614dab1867f16cea70ca1ec Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:17:35 +1000 Subject: [PATCH 054/426] test: fix cross-spawn stderr race on Windows CI (#23808) --- packages/opencode/test/effect/cross-spawn-spawner.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/effect/cross-spawn-spawner.test.ts b/packages/opencode/test/effect/cross-spawn-spawner.test.ts index 5990635aa..201d99866 100644 --- a/packages/opencode/test/effect/cross-spawn-spawner.test.ts +++ b/packages/opencode/test/effect/cross-spawn-spawner.test.ts @@ -169,7 +169,10 @@ describe("cross-spawn spawner", () => { 'process.stderr.write("stderr\\n", done)', ].join("\n"), ) - const [stdout, stderr] = yield* Effect.all([decodeByteStream(handle.stdout), decodeByteStream(handle.stderr)]) + const [stdout, stderr] = yield* Effect.all( + [decodeByteStream(handle.stdout), decodeByteStream(handle.stderr)], + { concurrency: 2 }, + ) expect(stdout).toBe("stdout") expect(stderr).toBe("stderr") }), From 71d196d3cd06d0efef7a319f43955c7cefc36e09 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 22 Apr 2026 08:18:44 +0000 Subject: [PATCH 055/426] chore: generate --- packages/opencode/test/effect/cross-spawn-spawner.test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/opencode/test/effect/cross-spawn-spawner.test.ts b/packages/opencode/test/effect/cross-spawn-spawner.test.ts index 201d99866..b4e52529c 100644 --- a/packages/opencode/test/effect/cross-spawn-spawner.test.ts +++ b/packages/opencode/test/effect/cross-spawn-spawner.test.ts @@ -169,10 +169,9 @@ describe("cross-spawn spawner", () => { 'process.stderr.write("stderr\\n", done)', ].join("\n"), ) - const [stdout, stderr] = yield* Effect.all( - [decodeByteStream(handle.stdout), decodeByteStream(handle.stderr)], - { concurrency: 2 }, - ) + const [stdout, stderr] = yield* Effect.all([decodeByteStream(handle.stdout), decodeByteStream(handle.stderr)], { + concurrency: 2, + }) expect(stdout).toBe("stdout") expect(stderr).toBe("stderr") }), From d884ab73d5516d301a740b2bdea174f6b485d6dc Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Wed, 22 Apr 2026 17:09:00 +0800 Subject: [PATCH 056/426] fix: consolidate project avatar source logic (#23819) --- .../src/components/dialog-edit-project.tsx | 20 +++++++++++++------ .../app/src/pages/layout/sidebar-items.tsx | 16 ++++++++------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx index 621d56646..8eb12daf5 100644 --- a/packages/app/src/components/dialog-edit-project.tsx +++ b/packages/app/src/components/dialog-edit-project.tsx @@ -12,6 +12,7 @@ import { type LocalProject, getAvatarColors } from "@/context/layout" import { getFilename } from "@opencode-ai/shared/util/path" import { Avatar } from "@opencode-ai/ui/avatar" import { useLanguage } from "@/context/language" +import { getProjectAvatarSource } from "@/pages/layout/sidebar-items" const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const @@ -144,7 +145,11 @@ export function DialogEditProject(props: { project: LocalProject }) { }} > } > - {language.t("dialog.project.edit.icon.alt")} + {(src) => ( + {language.t("dialog.project.edit.icon.alt")} + )}
{ + if (store.color === color && !props.project.icon?.url) return setStore("color", store.color === color ? undefined : color) }} > diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 88d50db3e..5170311a7 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -19,6 +19,14 @@ import { childSessionOnPath, hasProjectPermissions } from "./helpers" const OPENCODE_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750" +export function getProjectAvatarSource(id?: string, icon?: { color?: string; url?: string; override?: string }) { + return id === OPENCODE_PROJECT_ID + ? "https://opencode.ai/favicon.svg" + : icon?.color + ? undefined + : icon?.override || icon?.url +} + export const ProjectIcon = (props: { project: LocalProject; class?: string; notify?: boolean }): JSX.Element => { const globalSync = useGlobalSync() const notification = useNotification() @@ -42,13 +50,7 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
Date: Wed, 22 Apr 2026 16:35:13 +0530 Subject: [PATCH 057/426] fix(tui): fail fast on invalid session startup (#23837) --- packages/opencode/src/cli/cmd/tui/attach.ts | 16 ++++++ .../src/cli/cmd/tui/routes/session/index.tsx | 55 ++++++++++++------- packages/opencode/src/cli/cmd/tui/thread.ts | 14 +++++ .../src/cli/cmd/tui/validate-session.ts | 24 ++++++++ packages/opencode/src/util/error.ts | 9 +++ 5 files changed, 97 insertions(+), 21 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/validate-session.ts diff --git a/packages/opencode/src/cli/cmd/tui/attach.ts b/packages/opencode/src/cli/cmd/tui/attach.ts index 9a93f3f57..cb6b95a56 100644 --- a/packages/opencode/src/cli/cmd/tui/attach.ts +++ b/packages/opencode/src/cli/cmd/tui/attach.ts @@ -3,6 +3,8 @@ import { UI } from "@/cli/ui" import { tui } from "./app" import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32" import { TuiConfig } from "@/cli/cmd/tui/config/tui" +import { errorMessage } from "@/util/error" +import { validateSession } from "./validate-session" export const AttachCommand = cmd({ command: "attach ", @@ -65,6 +67,20 @@ export const AttachCommand = cmd({ return { Authorization: auth } })() const config = await TuiConfig.get() + + try { + await validateSession({ + url: args.url, + sessionID: args.session, + directory, + headers, + }) + } catch (error) { + UI.error(errorMessage(error)) + process.exitCode = 1 + return + } + await tui({ url: args.url, config, 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 06be5dfbe..2f5da1d23 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -68,6 +68,7 @@ import { Flag } from "@/flag/flag" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import parsers from "../../../../../../parsers-config.ts" import * as Clipboard from "../../util/clipboard" +import { errorMessage } from "@/util/error" import { Toast, useToast } from "../../ui/toast" import { useKV } from "../../context/kv.tsx" import * as Editor from "../../util/editor" @@ -180,31 +181,43 @@ export function Session() { const toast = useToast() const sdk = useSDK() - createEffect(async () => { - const previousWorkspace = project.workspace.current() - const result = await sdk.client.session.get({ sessionID: route.sessionID }, { throwOnError: true }) - if (!result.data) { + createEffect(() => { + const sessionID = route.sessionID + void (async () => { + const previousWorkspace = project.workspace.current() + const result = await sdk.client.session.get({ sessionID }, { throwOnError: true }) + if (!result.data) { + toast.show({ + message: `Session not found: ${sessionID}`, + variant: "error", + duration: 5000, + }) + navigate({ type: "home" }) + return + } + + if (result.data.workspaceID !== previousWorkspace) { + project.workspace.set(result.data.workspaceID) + + // Sync all the data for this workspace. Note that this + // workspace may not exist anymore which is why this is not + // fatal. If it doesn't we still want to show the session + // (which will be non-interactive) + try { + await sync.bootstrap({ fatal: false }) + } catch {} + } + await sync.session.sync(sessionID) + if (route.sessionID === sessionID && scroll) scroll.scrollBy(100_000) + })().catch((error) => { + if (route.sessionID !== sessionID) return toast.show({ - message: `Session not found: ${route.sessionID}`, + message: errorMessage(error), variant: "error", + duration: 5000, }) navigate({ type: "home" }) - return - } - - if (result.data.workspaceID !== previousWorkspace) { - project.workspace.set(result.data.workspaceID) - - // Sync all the data for this workspace. Note that this - // workspace may not exist anymore which is why this is not - // fatal. If it doesn't we still want to show the session - // (which will be non-interactive) - try { - await sync.bootstrap({ fatal: false }) - } catch (e) {} - } - await sync.session.sync(route.sessionID) - if (scroll) scroll.scrollBy(100_000) + }) }) let lastSwitch: string | undefined = undefined diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index e3e9eb811..a2a53ecaf 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -16,6 +16,7 @@ import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32" import { writeHeapSnapshot } from "v8" import { TuiConfig } from "./config/tui" import { OPENCODE_PROCESS_ROLE, OPENCODE_RUN_ID, ensureRunID, sanitizedProcessEnv } from "@/util/opencode-process" +import { validateSession } from "./validate-session" declare global { const OPENCODE_WORKER_PATH: string @@ -202,6 +203,19 @@ export const TuiThreadCommand = cmd({ events: createEventSource(client), } + try { + await validateSession({ + url: transport.url, + sessionID: args.session, + directory: cwd, + fetch: transport.fetch, + }) + } catch (error) { + UI.error(errorMessage(error)) + process.exitCode = 1 + return + } + setTimeout(() => { client.call("checkUpgrade", { directory: cwd }).catch(() => {}) }, 1000).unref?.() diff --git a/packages/opencode/src/cli/cmd/tui/validate-session.ts b/packages/opencode/src/cli/cmd/tui/validate-session.ts new file mode 100644 index 000000000..e2a21d51e --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/validate-session.ts @@ -0,0 +1,24 @@ +import { createOpencodeClient } from "@opencode-ai/sdk/v2" +import { SessionID } from "@/session/schema" + +export async function validateSession(input: { + url: string + sessionID?: string + directory?: string + fetch?: typeof fetch + headers?: RequestInit["headers"] +}) { + if (!input.sessionID) return + + const result = SessionID.zod.safeParse(input.sessionID) + if (!result.success) { + throw new Error(`Invalid session ID: ${result.error.issues.at(0)?.message ?? "unknown error"}`) + } + + await createOpencodeClient({ + baseUrl: input.url, + directory: input.directory, + fetch: input.fetch, + headers: input.headers, + }).session.get({ sessionID: result.data }, { throwOnError: true }) +} diff --git a/packages/opencode/src/util/error.ts b/packages/opencode/src/util/error.ts index 75fef9fc9..76cb9c7cf 100644 --- a/packages/opencode/src/util/error.ts +++ b/packages/opencode/src/util/error.ts @@ -26,6 +26,15 @@ export function errorMessage(error: unknown): string { return error.message } + if ( + isRecord(error) && + isRecord(error.data) && + typeof error.data.message === "string" && + error.data.message + ) { + return error.data.message + } + const text = String(error) if (text && text !== "[object Object]") return text From fa8b7bc4d269ac29310b410b30a9875b33023e66 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 22 Apr 2026 11:07:04 +0000 Subject: [PATCH 058/426] chore: generate --- packages/opencode/src/util/error.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/opencode/src/util/error.ts b/packages/opencode/src/util/error.ts index 76cb9c7cf..fbda2dc50 100644 --- a/packages/opencode/src/util/error.ts +++ b/packages/opencode/src/util/error.ts @@ -26,12 +26,7 @@ export function errorMessage(error: unknown): string { return error.message } - if ( - isRecord(error) && - isRecord(error.data) && - typeof error.data.message === "string" && - error.data.message - ) { + if (isRecord(error) && isRecord(error.data) && typeof error.data.message === "string" && error.data.message) { return error.data.message } From 574b2c21708baf872faa358152c1fa705437aa6c Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 22 Apr 2026 20:37:32 +0530 Subject: [PATCH 059/426] fix(session): improve session compaction (#23870) --- .../opencode/src/agent/prompt/compaction.txt | 19 +- packages/opencode/src/session/compaction.ts | 198 ++++++++++--- packages/opencode/src/session/message-v2.ts | 14 +- .../opencode/test/session/compaction.test.ts | 279 ++++++++++++++++-- .../opencode/test/session/message-v2.test.ts | 70 +++++ .../test/session/messages-pagination.test.ts | 64 ++++ 6 files changed, 561 insertions(+), 83 deletions(-) diff --git a/packages/opencode/src/agent/prompt/compaction.txt b/packages/opencode/src/agent/prompt/compaction.txt index c5831bb30..c7cb838bb 100644 --- a/packages/opencode/src/agent/prompt/compaction.txt +++ b/packages/opencode/src/agent/prompt/compaction.txt @@ -1,16 +1,9 @@ -You are a helpful AI assistant tasked with summarizing conversations. +You are an anchored context summarization assistant for coding sessions. -When asked to summarize, provide a detailed but concise summary of the older conversation history. -The most recent turns may be preserved verbatim outside your summary, so focus on information that would still be needed to continue the work with that recent context available. -Focus on information that would be helpful for continuing the conversation, including: -- What was done -- What is currently being worked on -- Which files are being modified -- What needs to be done next -- Key user requests, constraints, or preferences that should persist -- Important technical decisions and why they were made +Summarize only the conversation history you are given. The newest turns may be kept verbatim outside your summary, so focus on the older context that still matters for continuing the work. -Your summary should be comprehensive enough to provide context but concise enough to be quickly understood. +If the prompt includes a block, treat it as the current anchored summary. Update it with the new history by preserving still-true details, removing stale details, and merging in new facts. -Do not respond to any questions in the conversation, only output the summary. -Respond in the same language the user used in the conversation. +Always follow the exact output structure requested by the user prompt. Keep every section, preserve exact file paths and identifiers when known, and prefer terse bullets over paragraphs. + +Do not answer the conversation itself. Do not mention that you are summarizing, compacting, or merging context. Respond in the same language as the conversation. diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 037543064..defdb870d 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -32,16 +32,105 @@ export const Event = { export const PRUNE_MINIMUM = 20_000 export const PRUNE_PROTECT = 40_000 +const TOOL_OUTPUT_MAX_CHARS = 2_000 const PRUNE_PROTECTED_TOOLS = ["skill"] const DEFAULT_TAIL_TURNS = 2 const MIN_PRESERVE_RECENT_TOKENS = 2_000 const MAX_PRESERVE_RECENT_TOKENS = 8_000 +const SUMMARY_TEMPLATE = `Output exactly this Markdown structure and keep the section order unchanged: +--- +## Goal +- [single-sentence task summary] + +## Constraints & Preferences +- [user constraints, preferences, specs, or "(none)"] + +## Progress +### Done +- [completed work or "(none)"] + +### In Progress +- [current work or "(none)"] + +### Blocked +- [blockers or "(none)"] + +## Key Decisions +- [decision and why, or "(none)"] + +## Next Steps +- [ordered next actions or "(none)"] + +## Critical Context +- [important technical facts, errors, open questions, or "(none)"] + +## Relevant Files +- [file or directory path: why it matters, or "(none)"] +--- + +Rules: +- Keep every section, even when empty. +- Use terse bullets, not prose paragraphs. +- Preserve exact file paths, commands, error strings, and identifiers when known. +- Do not mention the summary process or that context was compacted.` type Turn = { start: number end: number id: MessageID } +type Tail = { + start: number + id: MessageID +} + +type CompletedCompaction = { + userIndex: number + assistantIndex: number + summary: string | undefined +} + +function summaryText(message: MessageV2.WithParts) { + const text = message.parts + .filter((part): part is MessageV2.TextPart => part.type === "text") + .map((part) => part.text.trim()) + .filter(Boolean) + .join("\n\n") + .trim() + return text || undefined +} + +function completedCompactions(messages: MessageV2.WithParts[]) { + const users = new Map() + for (let i = 0; i < messages.length; i++) { + const msg = messages[i] + if (msg.info.role !== "user") continue + if (!msg.parts.some((part) => part.type === "compaction")) continue + users.set(msg.info.id, i) + } + + return messages.flatMap((msg, assistantIndex): CompletedCompaction[] => { + if (msg.info.role !== "assistant") return [] + if (!msg.info.summary || !msg.info.finish || msg.info.error) return [] + const userIndex = users.get(msg.info.parentID) + if (userIndex === undefined) return [] + return [{ userIndex, assistantIndex, summary: summaryText(msg) }] + }) +} + +function buildPrompt(input: { previousSummary?: string; context: string[] }) { + const anchor = input.previousSummary + ? [ + "Update the anchored summary below using the conversation history above.", + "Preserve still-true details, remove stale details, and merge in the new facts.", + "", + input.previousSummary, + "", + ].join("\n") + : "Create a new anchored summary from the conversation history above." + return [anchor, SUMMARY_TEMPLATE, ...input.context].join("\n\n") +} + function preserveRecentBudget(input: { cfg: Config.Info; model: Provider.Model }) { return ( input.cfg.compaction?.preserve_recent_tokens ?? @@ -67,6 +156,31 @@ function turns(messages: MessageV2.WithParts[]) { return result } +function splitTurn(input: { + messages: MessageV2.WithParts[] + turn: Turn + model: Provider.Model + budget: number + estimate: (input: { messages: MessageV2.WithParts[]; model: Provider.Model }) => Effect.Effect +}) { + return Effect.gen(function* () { + if (input.budget <= 0) return undefined + if (input.turn.end - input.turn.start <= 1) return undefined + for (let start = input.turn.start + 1; start < input.turn.end; start++) { + const size = yield* input.estimate({ + messages: input.messages.slice(start, input.turn.end), + model: input.model, + }) + if (size > input.budget) continue + return { + start, + id: input.messages[start]!.info.id, + } satisfies Tail + } + return undefined + }) +} + export interface Interface { readonly isOverflow: (input: { tokens: MessageV2.Assistant["tokens"] @@ -147,18 +261,28 @@ export const layer: Layer.Layer< }), { concurrency: 1 }, ) - if (sizes.at(-1)! > budget) { - log.info("tail fallback", { budget, size: sizes.at(-1) }) - return { head: input.messages, tail_start_id: undefined } - } let total = 0 - let keep: Turn | undefined + let keep: Tail | undefined for (let i = recent.length - 1; i >= 0; i--) { + const turn = recent[i]! const size = sizes[i] - if (total + size > budget) break - total += size - keep = recent[i] + if (total + size <= budget) { + total += size + keep = { start: turn.start, id: turn.id } + continue + } + const remaining = budget - total + const split = yield* splitTurn({ + messages: input.messages, + turn, + model: input.model, + budget: remaining, + estimate, + }) + if (split) keep = split + else if (!keep) log.info("tail fallback", { budget, size, total }) + break } if (!keep || keep.start === 0) return { head: input.messages, tail_start_id: undefined } @@ -192,17 +316,15 @@ export const layer: Layer.Layer< if (msg.info.role === "assistant" && msg.info.summary) break loop for (let partIndex = msg.parts.length - 1; partIndex >= 0; partIndex--) { const part = msg.parts[partIndex] - if (part.type === "tool") - if (part.state.status === "completed") { - if (PRUNE_PROTECTED_TOOLS.includes(part.tool)) continue - if (part.state.time.compacted) break loop - const estimate = Token.estimate(part.state.output) - total += estimate - if (total > PRUNE_PROTECT) { - pruned += estimate - toPrune.push(part) - } - } + if (part.type !== "tool") continue + if (part.state.status !== "completed") continue + if (PRUNE_PROTECTED_TOOLS.includes(part.tool)) continue + if (part.state.time.compacted) break loop + const estimate = Token.estimate(part.state.output) + total += estimate + if (total <= PRUNE_PROTECT) continue + pruned += estimate + toPrune.push(part) } } @@ -263,8 +385,11 @@ export const layer: Layer.Layer< : yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID) const cfg = yield* config.get() const history = compactionPart && messages.at(-1)?.info.id === input.parentID ? messages.slice(0, -1) : messages + const prior = completedCompactions(history) + const hidden = new Set(prior.flatMap((item) => [item.userIndex, item.assistantIndex])) + const previousSummary = prior.at(-1)?.summary const selected = yield* select({ - messages: history, + messages: history.filter((_, index) => !hidden.has(index)), cfg, model, }) @@ -274,34 +399,13 @@ export const layer: Layer.Layer< { sessionID: input.sessionID }, { context: [], prompt: undefined }, ) - const defaultPrompt = `When constructing the summary, try to stick to this template: ---- -## Goal - -[What goal(s) is the user trying to accomplish?] - -## Instructions - -- [What important instructions did the user give you that are relevant] -- [If there is a plan or spec, include information about it so next agent can continue using it] - -## Discoveries - -[What notable things were learned during this conversation that would be useful for the next agent to know when continuing the work] - -## Accomplished - -[What work has been completed, what work is still in progress, and what work is left?] - -## Relevant files / directories - -[Construct a structured list of relevant files that have been read, edited, or created that pertain to the task at hand. If all the files in a directory are relevant, include the path to the directory.] ----` - - const prompt = compacting.prompt ?? [defaultPrompt, ...compacting.context].join("\n\n") + const nextPrompt = compacting.prompt ?? buildPrompt({ previousSummary, context: compacting.context }) const msgs = structuredClone(selected.head) yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs }) - const modelMessages = yield* MessageV2.toModelMessagesEffect(msgs, model, { stripMedia: true }) + const modelMessages = yield* MessageV2.toModelMessagesEffect(msgs, model, { + stripMedia: true, + toolOutputMaxChars: TOOL_OUTPUT_MAX_CHARS, + }) const ctx = yield* InstanceState.context const msg: MessageV2.Assistant = { id: MessageID.ascending(), @@ -345,7 +449,7 @@ export const layer: Layer.Layer< ...modelMessages, { role: "user", - content: [{ type: "text", text: prompt }], + content: [{ type: "text", text: nextPrompt }], }, ], model, diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 123f7b540..980dd4da8 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -319,6 +319,12 @@ export const ToolStateCompleted = Schema.Struct({ .pipe(withStatics((s) => ({ zod: zod(s) }))) export type ToolStateCompleted = Types.DeepMutable> +function truncateToolOutput(text: string, maxChars?: number) { + if (!maxChars || text.length <= maxChars) return text + const omitted = text.length - maxChars + return `${text.slice(0, maxChars)}\n[Tool output truncated for compaction: omitted ${omitted} chars]` +} + export const ToolStateError = Schema.Struct({ status: Schema.Literal("error"), input: Schema.Record(Schema.String, Schema.Any), @@ -700,7 +706,7 @@ function providerMeta(metadata: Record | undefined) { export const toModelMessagesEffect = Effect.fnUntraced(function* ( input: WithParts[], model: Provider.Model, - options?: { stripMedia?: boolean }, + options?: { stripMedia?: boolean; toolOutputMaxChars?: number }, ) { const result: UIMessage[] = [] const toolNames = new Set() @@ -839,7 +845,9 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* ( if (part.type === "tool") { toolNames.add(part.tool) if (part.state.status === "completed") { - const outputText = part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output + const outputText = part.state.time.compacted + ? "[Old tool result content cleared]" + : truncateToolOutput(part.state.output, options?.toolOutputMaxChars) const attachments = part.state.time.compacted || options?.stripMedia ? [] : (part.state.attachments ?? []) // For providers that don't support media in tool results, extract media files @@ -955,7 +963,7 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* ( export function toModelMessages( input: WithParts[], model: Provider.Model, - options?: { stripMedia?: boolean }, + options?: { stripMedia?: boolean; toolOutputMaxChars?: number }, ): Promise { return Effect.runPromise(toModelMessagesEffect(input, model, options).pipe(Effect.provide(EffectLogger.layer))) } diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 0e2b179f0..2188d8d7c 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -143,6 +143,43 @@ async function assistant(sessionID: SessionID, parentID: MessageID, root: string return msg } +async function summaryAssistant(sessionID: SessionID, parentID: MessageID, root: string, text: string) { + const msg: MessageV2.Assistant = { + id: MessageID.ascending(), + role: "assistant", + sessionID, + mode: "compaction", + agent: "compaction", + path: { cwd: root, root }, + cost: 0, + tokens: { + output: 0, + input: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: ref.modelID, + providerID: ref.providerID, + parentID, + summary: true, + time: { created: Date.now() }, + finish: "end_turn", + } + await svc.updateMessage(msg) + await svc.updatePart({ + id: PartID.ascending(), + messageID: msg.id, + sessionID, + type: "text", + text, + }) + return msg +} + +async function lastCompactionPart(sessionID: SessionID) { + return (await svc.messages({ sessionID })).at(-2)?.parts.find((item): item is MessageV2.CompactionPart => item.type === "compaction") +} + function fake( input: Parameters[0], result: "continue" | "compact", @@ -946,12 +983,9 @@ describe("session.compaction.process", () => { ), ) - const part = (await svc.messages({ sessionID: session.id })) - .at(-2) - ?.parts.find((item) => item.type === "compaction") - + const part = await lastCompactionPart(session.id) expect(part?.type).toBe("compaction") - if (part?.type === "compaction") expect(part.tail_start_id).toBe(keep.id) + expect(part?.tail_start_id).toBe(keep.id) } finally { await rt.dispose() } @@ -991,12 +1025,9 @@ describe("session.compaction.process", () => { ), ) - const part = (await svc.messages({ sessionID: session.id })) - .at(-2) - ?.parts.find((item) => item.type === "compaction") - + const part = await lastCompactionPart(session.id) expect(part?.type).toBe("compaction") - if (part?.type === "compaction") expect(part.tail_start_id).toBe(keep.id) + expect(part?.tail_start_id).toBe(keep.id) } finally { await rt.dispose() } @@ -1042,12 +1073,9 @@ describe("session.compaction.process", () => { ), ) - const part = (await svc.messages({ sessionID: session.id })) - .at(-2) - ?.parts.find((item) => item.type === "compaction") - + const part = await lastCompactionPart(session.id) expect(part?.type).toBe("compaction") - if (part?.type === "compaction") expect(part.tail_start_id).toBeUndefined() + expect(part?.tail_start_id).toBeUndefined() expect(captured).toContain("yyyy") } finally { await rt.dispose() @@ -1103,12 +1131,9 @@ describe("session.compaction.process", () => { ), ) - const part = (await svc.messages({ sessionID: session.id })) - .at(-2) - ?.parts.find((item) => item.type === "compaction") - + const part = await lastCompactionPart(session.id) expect(part?.type).toBe("compaction") - if (part?.type === "compaction") expect(part.tail_start_id).toBeUndefined() + expect(part?.tail_start_id).toBeUndefined() expect(captured).toContain("recent image turn") expect(captured).toContain("Attached image/png: big.png") } finally { @@ -1118,6 +1143,76 @@ describe("session.compaction.process", () => { }) }) + test("retains a split turn suffix when a later message fits the preserve token budget", async () => { + await using tmp = await tmpdir({ git: true }) + const stub = llm() + let captured = "" + stub.push( + reply("summary", (input) => { + captured = JSON.stringify(input.messages) + }), + ) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const session = await svc.create({}) + await user(session.id, "older") + const recent = await user(session.id, "recent turn") + const large = await assistant(session.id, recent.id, tmp.path) + await svc.updatePart({ + id: PartID.ascending(), + messageID: large.id, + sessionID: session.id, + type: "text", + text: "z".repeat(2_000), + }) + const keep = await assistant(session.id, recent.id, tmp.path) + await svc.updatePart({ + id: PartID.ascending(), + messageID: keep.id, + sessionID: session.id, + type: "text", + text: "keep tail", + }) + await SessionCompaction.create({ + sessionID: session.id, + agent: "build", + model: ref, + auto: false, + }) + + const rt = liveRuntime(stub.layer, wide(), cfg({ tail_turns: 1, preserve_recent_tokens: 100 })) + try { + const msgs = await svc.messages({ sessionID: session.id }) + const parent = msgs.at(-1)?.info.id + expect(parent).toBeTruthy() + await rt.runPromise( + SessionCompaction.Service.use((svc) => + svc.process({ + parentID: parent!, + messages: msgs, + sessionID: session.id, + auto: false, + }), + ), + ) + + const part = await lastCompactionPart(session.id) + expect(part?.type).toBe("compaction") + expect(part?.tail_start_id).toBe(keep.id) + expect(captured).toContain("zzzz") + expect(captured).not.toContain("keep tail") + + const filtered = MessageV2.filterCompacted(MessageV2.stream(session.id)) + expect(filtered[0]?.info.id).toBe(keep.id) + expect(filtered.map((msg) => msg.info.id)).not.toContain(large.id) + } finally { + await rt.dispose() + } + }, + }) + }) + test("allows plugins to disable synthetic continue prompt", async () => { await using tmp = await tmpdir() await Instance.provide({ @@ -1530,6 +1625,80 @@ describe("session.compaction.process", () => { }) }) + test("anchors repeated compactions with the previous summary", async () => { + const stub = llm() + let captured = "" + stub.push(reply("summary one")) + stub.push( + reply("summary two", (input) => { + captured = JSON.stringify(input.messages) + }), + ) + + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const session = await svc.create({}) + await user(session.id, "older context") + await user(session.id, "keep this turn") + await SessionCompaction.create({ + sessionID: session.id, + agent: "build", + model: ref, + auto: false, + }) + + const rt = liveRuntime(stub.layer, wide()) + try { + let msgs = await svc.messages({ sessionID: session.id }) + let parent = msgs.at(-1)?.info.id + expect(parent).toBeTruthy() + await rt.runPromise( + SessionCompaction.Service.use((svc) => + svc.process({ + parentID: parent!, + messages: msgs, + sessionID: session.id, + auto: false, + }), + ), + ) + + await user(session.id, "latest turn") + await SessionCompaction.create({ + sessionID: session.id, + agent: "build", + model: ref, + auto: false, + }) + + msgs = MessageV2.filterCompacted(MessageV2.stream(session.id)) + parent = msgs.at(-1)?.info.id + expect(parent).toBeTruthy() + await rt.runPromise( + SessionCompaction.Service.use((svc) => + svc.process({ + parentID: parent!, + messages: msgs, + sessionID: session.id, + auto: false, + }), + ), + ) + + expect(captured).toContain("") + expect(captured).toContain("summary one") + expect(captured.match(/summary one/g)?.length).toBe(1) + expect(captured).toContain("## Constraints & Preferences") + expect(captured).toContain("## Progress") + } finally { + await rt.dispose() + } + }, + }) + }) + test("keeps recent pre-compaction turns across repeated compactions", async () => { const stub = llm() stub.push(reply("summary one")) @@ -1604,6 +1773,76 @@ describe("session.compaction.process", () => { }, }) }) + + test("ignores previous summaries when sizing the retained tail", async () => { + await using tmp = await tmpdir() + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const session = await svc.create({}) + await user(session.id, "older") + const keep = await user(session.id, "keep this turn") + const keepReply = await assistant(session.id, keep.id, tmp.path) + await svc.updatePart({ + id: PartID.ascending(), + messageID: keepReply.id, + sessionID: session.id, + type: "text", + text: "keep reply", + }) + + await SessionCompaction.create({ + sessionID: session.id, + agent: "build", + model: ref, + auto: false, + }) + const firstCompaction = (await svc.messages({ sessionID: session.id })).at(-1)?.info.id + expect(firstCompaction).toBeTruthy() + await summaryAssistant(session.id, firstCompaction!, tmp.path, "summary ".repeat(800)) + + const recent = await user(session.id, "recent turn") + const recentReply = await assistant(session.id, recent.id, tmp.path) + await svc.updatePart({ + id: PartID.ascending(), + messageID: recentReply.id, + sessionID: session.id, + type: "text", + text: "recent reply", + }) + + await SessionCompaction.create({ + sessionID: session.id, + agent: "build", + model: ref, + auto: false, + }) + + const rt = runtime("continue", Plugin.defaultLayer, wide(), cfg({ tail_turns: 2, preserve_recent_tokens: 500 })) + try { + const msgs = await svc.messages({ sessionID: session.id }) + const parent = msgs.at(-1)?.info.id + expect(parent).toBeTruthy() + await rt.runPromise( + SessionCompaction.Service.use((svc) => + svc.process({ + parentID: parent!, + messages: msgs, + sessionID: session.id, + auto: false, + }), + ), + ) + + const part = await lastCompactionPart(session.id) + expect(part?.type).toBe("compaction") + expect(part?.tail_start_id).toBe(keep.id) + } finally { + await rt.dispose() + } + }, + }) + }) }) describe("util.token.estimate", () => { diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index 55ae65c56..231d58c21 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -585,6 +585,76 @@ describe("session.message-v2.toModelMessage", () => { ]) }) + test("truncates tool output when requested", async () => { + const userID = "m-user" + const assistantID = "m-assistant" + + const input: MessageV2.WithParts[] = [ + { + info: userInfo(userID), + parts: [ + { + ...basePart(userID, "u1"), + type: "text", + text: "run tool", + }, + ] as MessageV2.Part[], + }, + { + info: assistantInfo(assistantID, userID), + parts: [ + { + ...basePart(assistantID, "a1"), + type: "tool", + callID: "call-1", + tool: "bash", + state: { + status: "completed", + input: { cmd: "ls" }, + output: "abcdefghij", + title: "Bash", + metadata: {}, + time: { start: 0, end: 1 }, + }, + }, + ] as MessageV2.Part[], + }, + ] + + expect(await MessageV2.toModelMessages(input, model, { toolOutputMaxChars: 4 })).toStrictEqual([ + { + role: "user", + content: [{ type: "text", text: "run tool" }], + }, + { + role: "assistant", + content: [ + { + type: "tool-call", + toolCallId: "call-1", + toolName: "bash", + input: { cmd: "ls" }, + providerExecuted: undefined, + }, + ], + }, + { + role: "tool", + content: [ + { + type: "tool-result", + toolCallId: "call-1", + toolName: "bash", + output: { + type: "text", + value: "abcd\n[Tool output truncated for compaction: omitted 6 chars]", + }, + }, + ], + }, + ]) + }) + test("converts assistant tool error into error-text tool result", async () => { const userID = "m-user" const assistantID = "m-assistant" diff --git a/packages/opencode/test/session/messages-pagination.test.ts b/packages/opencode/test/session/messages-pagination.test.ts index d8dcf5e7c..df2d18b9f 100644 --- a/packages/opencode/test/session/messages-pagination.test.ts +++ b/packages/opencode/test/session/messages-pagination.test.ts @@ -837,6 +837,70 @@ describe("MessageV2.filterCompacted", () => { }) }) + test("retains an assistant tail when compaction starts inside a turn", async () => { + await Instance.provide({ + directory: root, + fn: async () => { + const session = await svc.create({}) + + const u1 = await addUser(session.id, "first") + const a1 = await addAssistant(session.id, u1, { finish: "end_turn" }) + await svc.updatePart({ + id: PartID.ascending(), + sessionID: session.id, + messageID: a1, + type: "text", + text: "first reply", + }) + + const u2 = await addUser(session.id, "second") + const a2 = await addAssistant(session.id, u2, { finish: "end_turn" }) + await svc.updatePart({ + id: PartID.ascending(), + sessionID: session.id, + messageID: a2, + type: "text", + text: "second reply", + }) + const a3 = await addAssistant(session.id, u2, { finish: "end_turn" }) + await svc.updatePart({ + id: PartID.ascending(), + sessionID: session.id, + messageID: a3, + type: "text", + text: "tail reply", + }) + + const c1 = await addUser(session.id) + await addCompactionPart(session.id, c1, a3) + const s1 = await addAssistant(session.id, c1, { summary: true, finish: "end_turn" }) + await svc.updatePart({ + id: PartID.ascending(), + sessionID: session.id, + messageID: s1, + type: "text", + text: "summary", + }) + + const u3 = await addUser(session.id, "third") + const a4 = await addAssistant(session.id, u3, { finish: "end_turn" }) + await svc.updatePart({ + id: PartID.ascending(), + sessionID: session.id, + messageID: a4, + type: "text", + text: "third reply", + }) + + const result = MessageV2.filterCompacted(MessageV2.stream(session.id)) + + expect(result.map((item) => item.info.id)).toEqual([a3, c1, s1, u3, a4]) + + await svc.remove(session.id) + }, + }) + }) + test("prefers latest compaction boundary when repeated compactions exist", async () => { await Instance.provide({ directory: root, From 504fd1b373d0b35f39ee40b07fdb6957454de6b1 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 22 Apr 2026 15:09:51 +0000 Subject: [PATCH 060/426] chore: generate --- packages/opencode/test/session/compaction.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 2188d8d7c..037613d46 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -177,7 +177,9 @@ async function summaryAssistant(sessionID: SessionID, parentID: MessageID, root: } async function lastCompactionPart(sessionID: SessionID) { - return (await svc.messages({ sessionID })).at(-2)?.parts.find((item): item is MessageV2.CompactionPart => item.type === "compaction") + return (await svc.messages({ sessionID })) + .at(-2) + ?.parts.find((item): item is MessageV2.CompactionPart => item.type === "compaction") } function fake( From 3a082a0efd5eaa5296c929241bfe565d460c277b Mon Sep 17 00:00:00 2001 From: "Steven T. Cramer" Date: Wed, 22 Apr 2026 23:02:10 +0700 Subject: [PATCH 061/426] fix(project): use git common dir for bare repo project cache (#19054) --- packages/opencode/src/project/project.ts | 12 +-- .../opencode/test/project/project.test.ts | 84 +++++++++++++++++++ 2 files changed, 90 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 6a2132274..d628f87f9 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -207,13 +207,13 @@ export const layer: Layer.Layer< vcs: fakeVcs, } } - const worktree = (() => { - const common = resolveGitPath(sandbox, commonDir.text.trim()) - return common === sandbox ? sandbox : pathSvc.dirname(common) - })() + const common = resolveGitPath(sandbox, commonDir.text.trim()) + const bareCheck = yield* git(["config", "--bool", "core.bare"], { cwd: sandbox }) + const isBareRepo = bareCheck.code === 0 && bareCheck.text.trim() === "true" + const worktree = common === sandbox ? sandbox : isBareRepo ? common : pathSvc.dirname(common) if (id == null) { - id = yield* readCachedProjectId(pathSvc.join(worktree, ".git")) + id = yield* readCachedProjectId(common) } if (!id) { @@ -226,7 +226,7 @@ export const layer: Layer.Layer< id = roots[0] ? ProjectID.make(roots[0]) : undefined if (id) { - yield* fs.writeFileString(pathSvc.join(worktree, ".git", "opencode"), id).pipe(Effect.ignore) + yield* fs.writeFileString(pathSvc.join(common, "opencode"), id).pipe(Effect.ignore) } } diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index 4dc9ee5ef..4664b6c25 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -472,3 +472,87 @@ describe("Project.addSandbox and Project.removeSandbox", () => { expect(events.some((e) => e.payload.type === Project.Event.Updated.type)).toBe(true) }) }) + +describe("Project.fromDirectory with bare repos", () => { + test("worktree from bare repo should cache in bare repo, not parent", async () => { + await using tmp = await tmpdir({ git: true }) + + const parentDir = path.dirname(tmp.path) + const barePath = path.join(parentDir, `bare-${Date.now()}.git`) + const worktreePath = path.join(parentDir, `worktree-${Date.now()}`) + + try { + await $`git clone --bare ${tmp.path} ${barePath}`.quiet() + await $`git worktree add ${worktreePath} HEAD`.cwd(barePath).quiet() + + const { project } = await run((svc) => svc.fromDirectory(worktreePath)) + + expect(project.id).not.toBe(ProjectID.global) + expect(project.worktree).toBe(barePath) + + const correctCache = path.join(barePath, "opencode") + const wrongCache = path.join(parentDir, ".git", "opencode") + + expect(await Bun.file(correctCache).exists()).toBe(true) + expect(await Bun.file(wrongCache).exists()).toBe(false) + } finally { + await $`rm -rf ${barePath} ${worktreePath}`.quiet().nothrow() + } + }) + + test("different bare repos under same parent should not share project ID", async () => { + await using tmp1 = await tmpdir({ git: true }) + await using tmp2 = await tmpdir({ git: true }) + + const parentDir = path.dirname(tmp1.path) + const bareA = path.join(parentDir, `bare-a-${Date.now()}.git`) + const bareB = path.join(parentDir, `bare-b-${Date.now()}.git`) + const worktreeA = path.join(parentDir, `wt-a-${Date.now()}`) + const worktreeB = path.join(parentDir, `wt-b-${Date.now()}`) + + try { + await $`git clone --bare ${tmp1.path} ${bareA}`.quiet() + await $`git clone --bare ${tmp2.path} ${bareB}`.quiet() + await $`git worktree add ${worktreeA} HEAD`.cwd(bareA).quiet() + await $`git worktree add ${worktreeB} HEAD`.cwd(bareB).quiet() + + const { project: projA } = await run((svc) => svc.fromDirectory(worktreeA)) + const { project: projB } = await run((svc) => svc.fromDirectory(worktreeB)) + + expect(projA.id).not.toBe(projB.id) + + const cacheA = path.join(bareA, "opencode") + const cacheB = path.join(bareB, "opencode") + const wrongCache = path.join(parentDir, ".git", "opencode") + + expect(await Bun.file(cacheA).exists()).toBe(true) + expect(await Bun.file(cacheB).exists()).toBe(true) + expect(await Bun.file(wrongCache).exists()).toBe(false) + } finally { + await $`rm -rf ${bareA} ${bareB} ${worktreeA} ${worktreeB}`.quiet().nothrow() + } + }) + + test("bare repo without .git suffix is still detected via core.bare", async () => { + await using tmp = await tmpdir({ git: true }) + + const parentDir = path.dirname(tmp.path) + const barePath = path.join(parentDir, `bare-no-suffix-${Date.now()}`) + const worktreePath = path.join(parentDir, `worktree-${Date.now()}`) + + try { + await $`git clone --bare ${tmp.path} ${barePath}`.quiet() + await $`git worktree add ${worktreePath} HEAD`.cwd(barePath).quiet() + + const { project } = await run((svc) => svc.fromDirectory(worktreePath)) + + expect(project.id).not.toBe(ProjectID.global) + expect(project.worktree).toBe(barePath) + + const correctCache = path.join(barePath, "opencode") + expect(await Bun.file(correctCache).exists()).toBe(true) + } finally { + await $`rm -rf ${barePath} ${worktreePath}`.quiet().nothrow() + } + }) +}) From e9b1d3b940d6369a1c9bd492daf168c933cdfdab Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 23 Apr 2026 00:28:20 +0800 Subject: [PATCH 062/426] docs: add MiMo V2.5 to Go pages (#23876) --- packages/console/app/src/i18n/ar.ts | 8 ++++---- packages/console/app/src/i18n/br.ts | 8 ++++---- packages/console/app/src/i18n/da.ts | 8 ++++---- packages/console/app/src/i18n/de.ts | 8 ++++---- packages/console/app/src/i18n/en.ts | 8 ++++---- packages/console/app/src/i18n/es.ts | 8 ++++---- packages/console/app/src/i18n/fr.ts | 8 ++++---- packages/console/app/src/i18n/it.ts | 8 ++++---- packages/console/app/src/i18n/ja.ts | 8 ++++---- packages/console/app/src/i18n/ko.ts | 8 ++++---- packages/console/app/src/i18n/no.ts | 8 ++++---- packages/console/app/src/i18n/pl.ts | 8 ++++---- packages/console/app/src/i18n/ru.ts | 8 ++++---- packages/console/app/src/i18n/th.ts | 8 ++++---- packages/console/app/src/i18n/tr.ts | 8 ++++---- packages/console/app/src/i18n/zh.ts | 8 ++++---- packages/console/app/src/i18n/zht.ts | 8 ++++---- packages/console/app/src/routes/go/index.tsx | 4 +++- .../src/routes/workspace/[id]/go/lite-section.tsx | 6 ++++-- packages/web/src/content/docs/ar/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/bs/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/da/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/de/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/es/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/fr/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/it/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/ja/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/ko/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/nb/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/pl/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/pt-br/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/ru/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/th/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/tr/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/zh-cn/go.mdx | 12 ++++++++++-- packages/web/src/content/docs/zh-tw/go.mdx | 12 ++++++++++-- 37 files changed, 255 insertions(+), 107 deletions(-) diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index 73c07c677..a7883cfe4 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -249,7 +249,7 @@ export const dict = { "go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع", "go.meta.description": - "يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.", + "يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.", "go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع", "go.hero.body": "يجلب Go البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر، حتى تتمكن من البناء باستخدام وكلاء أقوياء دون القلق بشأن التكلفة أو التوفر.", @@ -300,7 +300,7 @@ export const dict = { "go.problem.item2": "حدود سخية ووصول موثوق", "go.problem.item3": "مصمم لأكبر عدد ممكن من المبرمجين", "go.problem.item4": - "يتضمن GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7", + "يتضمن GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7", "go.how.title": "كيف يعمل Go", "go.how.body": "يبدأ Go من $5 للشهر الأول، ثم $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.", "go.how.step1.title": "أنشئ حسابًا", @@ -324,7 +324,7 @@ export const dict = { "go.faq.a2": "يتضمن Go النماذج المدرجة أدناه، مع حدود سخية وإتاحة موثوقة.", "go.faq.q3": "هل Go هو نفسه Zen؟", "go.faq.a3": - "لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.", + "لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.", "go.faq.q4": "كم تكلفة Go؟", "go.faq.a4.p1.beforePricing": "تكلفة Go", "go.faq.a4.p1.pricingLink": "$5 للشهر الأول", @@ -347,7 +347,7 @@ export const dict = { "go.faq.q9": "ما الفرق بين النماذج المجانية وGo؟", "go.faq.a9": - "تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).", + "تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).", "zen.api.error.rateLimitExceeded": "تم تجاوز حد الطلبات. يرجى المحاولة مرة أخرى لاحقًا.", "zen.api.error.modelNotSupported": "النموذج {{model}} غير مدعوم", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index d79b8350a..cf7b68d25 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos", "go.meta.description": - "O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", + "O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", "go.hero.title": "Modelos de codificação de baixo custo para todos", "go.hero.body": "O Go traz a codificação com agentes para programadores em todo o mundo. Oferecendo limites generosos e acesso confiável aos modelos de código aberto mais capazes, para que você possa construir com agentes poderosos sem se preocupar com custos ou disponibilidade.", @@ -305,7 +305,7 @@ export const dict = { "go.problem.item2": "Limites generosos e acesso confiável", "go.problem.item3": "Feito para o maior número possível de programadores", "go.problem.item4": - "Inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7", + "Inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7", "go.how.title": "Como o Go funciona", "go.how.body": "O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.", @@ -331,7 +331,7 @@ export const dict = { "go.faq.a2": "O Go inclui os modelos listados abaixo, com limites generosos e acesso confiável.", "go.faq.q3": "O Go é o mesmo que o Zen?", "go.faq.a3": - "Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", + "Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", "go.faq.q4": "Quanto custa o Go?", "go.faq.a4.p1.beforePricing": "O Go custa", "go.faq.a4.p1.pricingLink": "$5 no primeiro mês", @@ -355,7 +355,7 @@ export const dict = { "go.faq.q9": "Qual a diferença entre os modelos gratuitos e o Go?", "go.faq.a9": - "Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).", + "Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).", "zen.api.error.rateLimitExceeded": "Limite de taxa excedido. Por favor, tente novamente mais tarde.", "zen.api.error.modelNotSupported": "Modelo {{model}} não suportado", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index e80698396..90eff469a 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle", "go.meta.description": - "Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", + "Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", "go.hero.title": "Kodningsmodeller til lav pris for alle", "go.hero.body": "Go bringer agentisk kodning til programmører over hele verden. Med generøse grænser og pålidelig adgang til de mest kapable open source-modeller, så du kan bygge med kraftfulde agenter uden at bekymre dig om omkostninger eller tilgængelighed.", @@ -302,7 +302,7 @@ export const dict = { "go.problem.item2": "Generøse grænser og pålidelig adgang", "go.problem.item3": "Bygget til så mange programmører som muligt", "go.problem.item4": - "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7", + "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7", "go.how.title": "Hvordan Go virker", "go.how.body": "Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.", @@ -328,7 +328,7 @@ export const dict = { "go.faq.a2": "Go inkluderer modellerne nedenfor med generøse grænser og pålidelig adgang.", "go.faq.q3": "Er Go det samme som Zen?", "go.faq.a3": - "Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", + "Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", "go.faq.q4": "Hvad koster Go?", "go.faq.a4.p1.beforePricing": "Go koster", "go.faq.a4.p1.pricingLink": "$5 første måned", @@ -351,7 +351,7 @@ export const dict = { "go.faq.q9": "Hvad er forskellen på gratis modeller og Go?", "go.faq.a9": - "Gratis modeller inkluderer Big Pickle plus salgsfremmende modeller tilgængelige på det tidspunkt, med en kvote på 200 forespørgsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med højere anmodningskvoter håndhævet over rullende vinduer (5-timers, ugentlig og månedlig), nogenlunde svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (faktiske anmodningstal varierer efter model og brug).", + "Gratis modeller inkluderer Big Pickle plus salgsfremmende modeller tilgængelige på det tidspunkt, med en kvote på 200 forespørgsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med højere anmodningskvoter håndhævet over rullende vinduer (5-timers, ugentlig og månedlig), nogenlunde svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (faktiske anmodningstal varierer efter model og brug).", "zen.api.error.rateLimitExceeded": "Hastighedsgrænse overskredet. Prøv venligst igen senere.", "zen.api.error.modelNotSupported": "Model {{model}} understøttes ikke", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index bdd47e77c..af339802f 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle", "go.meta.description": - "Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.", + "Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.", "go.hero.title": "Kostengünstige Coding-Modelle für alle", "go.hero.body": "Go bringt Agentic Coding zu Programmierern auf der ganzen Welt. Mit großzügigen Limits und zuverlässigem Zugang zu den leistungsfähigsten Open-Source-Modellen, damit du mit leistungsstarken Agenten entwickeln kannst, ohne dir Gedanken über Kosten oder Verfügbarkeit zu machen.", @@ -304,7 +304,7 @@ export const dict = { "go.problem.item2": "Großzügige Limits und zuverlässiger Zugang", "go.problem.item3": "Für so viele Programmierer wie möglich gebaut", "go.problem.item4": - "Beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7", + "Beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7", "go.how.title": "Wie Go funktioniert", "go.how.body": "Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.", @@ -330,7 +330,7 @@ export const dict = { "go.faq.a2": "Go umfasst die unten aufgeführten Modelle mit großzügigen Limits und zuverlässigem Zugriff.", "go.faq.q3": "Ist Go dasselbe wie Zen?", "go.faq.a3": - "Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.", + "Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.", "go.faq.q4": "Wie viel kostet Go?", "go.faq.a4.p1.beforePricing": "Go kostet", "go.faq.a4.p1.pricingLink": "$5 im ersten Monat", @@ -354,7 +354,7 @@ export const dict = { "go.faq.q9": "Was ist der Unterschied zwischen kostenlosen Modellen und Go?", "go.faq.a9": - "Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7 mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).", + "Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7 mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).", "zen.api.error.rateLimitExceeded": "Ratenlimit überschritten. Bitte versuche es später erneut.", "zen.api.error.modelNotSupported": "Modell {{model}} wird nicht unterstützt", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index a242ff101..f5cc954e5 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -248,7 +248,7 @@ export const dict = { "go.title": "OpenCode Go | Low cost coding models for everyone", "go.meta.description": - "Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.", + "Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.", "go.banner.badge": "3x", "go.banner.text": "Kimi K2.6 gets 3× usage limits through April 27", "go.hero.title": "Low cost coding models for everyone", @@ -298,7 +298,7 @@ export const dict = { "go.problem.item2": "Generous limits and reliable access", "go.problem.item3": "Built for as many programmers as possible", "go.problem.item4": - "Includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7", + "Includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7", "go.how.title": "How Go works", "go.how.body": "Go starts at $5 for your first month, then $10/month. You can use it with OpenCode or any agent.", "go.how.step1.title": "Create an account", @@ -323,7 +323,7 @@ export const dict = { "go.faq.a2": "Go includes the models listed below, with generous limits and reliable access.", "go.faq.q3": "Is Go the same as Zen?", "go.faq.a3": - "No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.", + "No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.", "go.faq.q4": "How much does Go cost?", "go.faq.a4.p1.beforePricing": "Go costs", "go.faq.a4.p1.pricingLink": "$5 first month", @@ -347,7 +347,7 @@ export const dict = { "go.faq.q9": "What is the difference between free models and Go?", "go.faq.a9": - "Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7 with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).", + "Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7 with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).", "zen.api.error.rateLimitExceeded": "Rate limit exceeded. Please try again later.", "zen.api.error.modelNotSupported": "Model {{model}} not supported", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index ddeff684b..fb718e054 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -254,7 +254,7 @@ export const dict = { "go.title": "OpenCode Go | Modelos de programación de bajo coste para todos", "go.meta.description": - "Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.", + "Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.", "go.hero.title": "Modelos de programación de bajo coste para todos", "go.hero.body": "Go lleva la programación agéntica a programadores de todo el mundo. Ofrece límites generosos y acceso fiable a los modelos de código abierto más capaces, para que puedas crear con agentes potentes sin preocuparte por el coste o la disponibilidad.", @@ -306,7 +306,7 @@ export const dict = { "go.problem.item2": "Límites generosos y acceso fiable", "go.problem.item3": "Creado para tantos programadores como sea posible", "go.problem.item4": - "Incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7", + "Incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7", "go.how.title": "Cómo funciona Go", "go.how.body": "Go comienza en $5 el primer mes, luego 10 $/mes. Puedes usarlo con OpenCode o cualquier agente.", "go.how.step1.title": "Crear una cuenta", @@ -331,7 +331,7 @@ export const dict = { "go.faq.a2": "Go incluye los modelos que se indican abajo, con límites generosos y acceso confiable.", "go.faq.q3": "¿Es Go lo mismo que Zen?", "go.faq.a3": - "No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.", + "No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.", "go.faq.q4": "¿Cuánto cuesta Go?", "go.faq.a4.p1.beforePricing": "Go cuesta", "go.faq.a4.p1.pricingLink": "$5 el primer mes", @@ -355,7 +355,7 @@ export const dict = { "go.faq.q9": "¿Cuál es la diferencia entre los modelos gratuitos y Go?", "go.faq.a9": - "Los modelos gratuitos incluyen Big Pickle más modelos promocionales disponibles en el momento, con una cuota de 200 solicitudes/día. Go incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7 con cuotas de solicitud más altas aplicadas a través de ventanas móviles (5 horas, semanal y mensual), aproximadamente equivalente a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (los recuentos reales de solicitudes varían según el modelo y el uso).", + "Los modelos gratuitos incluyen Big Pickle más modelos promocionales disponibles en el momento, con una cuota de 200 solicitudes/día. Go incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7 con cuotas de solicitud más altas aplicadas a través de ventanas móviles (5 horas, semanal y mensual), aproximadamente equivalente a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (los recuentos reales de solicitudes varían según el modelo y el uso).", "zen.api.error.rateLimitExceeded": "Límite de tasa excedido. Por favor, inténtalo de nuevo más tarde.", "zen.api.error.modelNotSupported": "Modelo {{model}} no soportado", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index df892c98d..976d93a29 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -255,7 +255,7 @@ export const dict = { "go.title": "OpenCode Go | Modèles de code à faible coût pour tous", "go.meta.description": - "Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites de requêtes généreuses sur 5 heures pour GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.", + "Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites de requêtes généreuses sur 5 heures pour GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.", "go.hero.title": "Modèles de code à faible coût pour tous", "go.hero.body": "Go apporte le codage agentique aux programmeurs du monde entier. Offrant des limites généreuses et un accès fiable aux modèles open source les plus capables, pour que vous puissiez construire avec des agents puissants sans vous soucier du coût ou de la disponibilité.", @@ -306,7 +306,7 @@ export const dict = { "go.problem.item2": "Limites généreuses et accès fiable", "go.problem.item3": "Conçu pour autant de programmeurs que possible", "go.problem.item4": - "Inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7", + "Inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7", "go.how.title": "Comment fonctionne Go", "go.how.body": "Go commence à $5 pour le premier mois, puis 10 $/mois. Vous pouvez l'utiliser avec OpenCode ou n'importe quel agent.", @@ -332,7 +332,7 @@ export const dict = { "go.faq.a2": "Go inclut les modèles ci-dessous, avec des limites généreuses et un accès fiable.", "go.faq.q3": "Est-ce que Go est la même chose que Zen ?", "go.faq.a3": - "Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable aux modèles open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.", + "Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable aux modèles open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.", "go.faq.q4": "Combien coûte Go ?", "go.faq.a4.p1.beforePricing": "Go coûte", "go.faq.a4.p1.pricingLink": "$5 le premier mois", @@ -355,7 +355,7 @@ export const dict = { "Oui, vous pouvez utiliser Go avec n'importe quel agent. Suivez les instructions de configuration dans votre agent de code préféré.", "go.faq.q9": "Quelle est la différence entre les modèles gratuits et Go ?", "go.faq.a9": - "Les modèles gratuits incluent Big Pickle ainsi que des modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7 avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalent à 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois (le nombre réel de requêtes varie selon le modèle et l'utilisation).", + "Les modèles gratuits incluent Big Pickle ainsi que des modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7 avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalent à 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois (le nombre réel de requêtes varie selon le modèle et l'utilisation).", "zen.api.error.rateLimitExceeded": "Limite de débit dépassée. Veuillez réessayer plus tard.", "zen.api.error.modelNotSupported": "Modèle {{model}} non pris en charge", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 67a73aaee..6069ad73c 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Modelli di coding a basso costo per tutti", "go.meta.description": - "Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", + "Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", "go.hero.title": "Modelli di coding a basso costo per tutti", "go.hero.body": "Go porta il coding agentico ai programmatori di tutto il mondo. Offrendo limiti generosi e un accesso affidabile ai modelli open source più capaci, in modo da poter costruire con agenti potenti senza preoccuparsi dei costi o della disponibilità.", @@ -302,7 +302,7 @@ export const dict = { "go.problem.item2": "Limiti generosi e accesso affidabile", "go.problem.item3": "Costruito per il maggior numero possibile di programmatori", "go.problem.item4": - "Include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7", + "Include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7", "go.how.title": "Come funziona Go", "go.how.body": "Go inizia a $5 per il primo mese, poi $10/mese. Puoi usarlo con OpenCode o qualsiasi agente.", "go.how.step1.title": "Crea un account", @@ -327,7 +327,7 @@ export const dict = { "go.faq.a2": "Go include i modelli elencati di seguito, con limiti generosi e accesso affidabile.", "go.faq.q3": "Go è lo stesso di Zen?", "go.faq.a3": - "No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e accesso affidabile ai modelli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", + "No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e accesso affidabile ai modelli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", "go.faq.q4": "Quanto costa Go?", "go.faq.a4.p1.beforePricing": "Go costa", "go.faq.a4.p1.pricingLink": "$5 il primo mese", @@ -351,7 +351,7 @@ export const dict = { "go.faq.q9": "Qual è la differenza tra i modelli gratuiti e Go?", "go.faq.a9": - "I modelli gratuiti includono Big Pickle più modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a $12 ogni 5 ore, $30 a settimana e $60 al mese (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).", + "I modelli gratuiti includono Big Pickle più modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a $12 ogni 5 ore, $30 a settimana e $60 al mese (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).", "zen.api.error.rateLimitExceeded": "Limite di richieste superato. Riprova più tardi.", "zen.api.error.modelNotSupported": "Modello {{model}} non supportato", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index 541fdd56c..dcf2f9b52 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -250,7 +250,7 @@ export const dict = { "go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル", "go.meta.description": - "Goは最初の月$5、その後$10/月で、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7に対して5時間のゆとりあるリクエスト上限があります。", + "Goは最初の月$5、その後$10/月で、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7に対して5時間のゆとりあるリクエスト上限があります。", "go.hero.title": "すべての人のための低価格なコーディングモデル", "go.hero.body": "Goは、世界中のプログラマーにエージェント型コーディングをもたらします。最も高性能なオープンソースモデルへの十分な制限と安定したアクセスを提供し、コストや可用性を気にすることなく強力なエージェントで構築できます。", @@ -302,7 +302,7 @@ export const dict = { "go.problem.item2": "十分な制限と安定したアクセス", "go.problem.item3": "できるだけ多くのプログラマーのために構築", "go.problem.item4": - "GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7を含む", + "GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7を含む", "go.how.title": "Goの仕組み", "go.how.body": "Goは最初の月$5、その後$10/月で始まります。OpenCodeまたは任意のエージェントで使えます。", "go.how.step1.title": "アカウントを作成", @@ -327,7 +327,7 @@ export const dict = { "go.faq.a2": "Go には、十分な利用上限と安定したアクセスを備えた、以下のモデルが含まれます。", "go.faq.q3": "GoはZenと同じですか?", "go.faq.a3": - "いいえ。Zenは従量課金制ですが、Goは最初の月$5、その後$10/月で始まり、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7のオープンソースモデルに対して、ゆとりある上限と信頼できるアクセスを提供します。", + "いいえ。Zenは従量課金制ですが、Goは最初の月$5、その後$10/月で始まり、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7のオープンソースモデルに対して、ゆとりある上限と信頼できるアクセスを提供します。", "go.faq.q4": "Goの料金は?", "go.faq.a4.p1.beforePricing": "Goは", "go.faq.a4.p1.pricingLink": "最初の月$5", @@ -351,7 +351,7 @@ export const dict = { "go.faq.q9": "無料モデルとGoの違いは何ですか?", "go.faq.a9": - "無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。GoにはGLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7が含まれ、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。これは概算で5時間あたり$12、週間$30、月間$60相当です(実際のリクエスト数はモデルと使用状況により異なります)。", + "無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。GoにはGLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7が含まれ、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。これは概算で5時間あたり$12、週間$30、月間$60相当です(実際のリクエスト数はモデルと使用状況により異なります)。", "zen.api.error.rateLimitExceeded": "レート制限を超えました。後でもう一度お試しください。", "zen.api.error.modelNotSupported": "モデル {{model}} はサポートされていません", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index 5d459425b..f2a67fbba 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -247,7 +247,7 @@ export const dict = { "go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델", "go.meta.description": - "Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7에 대해 넉넉한 5시간 요청 한도를 제공합니다.", + "Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7에 대해 넉넉한 5시간 요청 한도를 제공합니다.", "go.hero.title": "모두를 위한 저비용 코딩 모델", "go.hero.body": "Go는 전 세계 프로그래머들에게 에이전트 코딩을 제공합니다. 가장 유능한 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공하므로, 비용이나 가용성 걱정 없이 강력한 에이전트로 빌드할 수 있습니다.", @@ -299,7 +299,7 @@ export const dict = { "go.problem.item2": "넉넉한 한도와 안정적인 액세스", "go.problem.item3": "가능한 한 많은 프로그래머를 위해 제작됨", "go.problem.item4": - "GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 포함", + "GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 포함", "go.how.title": "Go 작동 방식", "go.how.body": "Go는 첫 달 $5, 이후 $10/월로 시작합니다. OpenCode 또는 어떤 에이전트와도 함께 사용할 수 있습니다.", "go.how.step1.title": "계정 생성", @@ -323,7 +323,7 @@ export const dict = { "go.faq.a2": "Go에는 넉넉한 한도와 안정적인 액세스를 제공하는 아래 모델이 포함됩니다.", "go.faq.q3": "Go는 Zen과 같은가요?", "go.faq.a3": - "아니요. Zen은 종량제인 반면, Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.", + "아니요. Zen은 종량제인 반면, Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.", "go.faq.q4": "Go 비용은 얼마인가요?", "go.faq.a4.p1.beforePricing": "Go 비용은", "go.faq.a4.p1.pricingLink": "첫 달 $5", @@ -346,7 +346,7 @@ export const dict = { "go.faq.q9": "무료 모델과 Go의 차이점은 무엇인가요?", "go.faq.a9": - "무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7를 포함하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 이는 대략 5시간당 $12, 주당 $30, 월 $60에 해당합니다(실제 요청 수는 모델 및 사용량에 따라 다름).", + "무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7를 포함하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 이는 대략 5시간당 $12, 주당 $30, 월 $60에 해당합니다(실제 요청 수는 모델 및 사용량에 따라 다름).", "zen.api.error.rateLimitExceeded": "속도 제한을 초과했습니다. 나중에 다시 시도해 주세요.", "zen.api.error.modelNotSupported": "{{model}} 모델은 지원되지 않습니다", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index af2b018b0..0207a5776 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Rimelige kodemodeller for alle", "go.meta.description": - "Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", + "Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", "go.hero.title": "Rimelige kodemodeller for alle", "go.hero.body": "Go bringer agent-koding til programmerere over hele verden. Med rause grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene, kan du bygge med kraftige agenter uten å bekymre deg for kostnader eller tilgjengelighet.", @@ -302,7 +302,7 @@ export const dict = { "go.problem.item2": "Rause grenser og pålitelig tilgang", "go.problem.item3": "Bygget for så mange programmerere som mulig", "go.problem.item4": - "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7", + "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7", "go.how.title": "Hvordan Go fungerer", "go.how.body": "Go starter på $5 for den første måneden, deretter $10/måned. Du kan bruke det med OpenCode eller hvilken som helst agent.", @@ -328,7 +328,7 @@ export const dict = { "go.faq.a2": "Go inkluderer modellene nedenfor, med høye grenser og pålitelig tilgang.", "go.faq.q3": "Er Go det samme som Zen?", "go.faq.a3": - "Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til åpen kildekode-modellene GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", + "Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til åpen kildekode-modellene GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", "go.faq.q4": "Hva koster Go?", "go.faq.a4.p1.beforePricing": "Go koster", "go.faq.a4.p1.pricingLink": "$5 første måned", @@ -352,7 +352,7 @@ export const dict = { "go.faq.q9": "Hva er forskjellen mellom gratis modeller og Go?", "go.faq.a9": - "Gratis modeller inkluderer Big Pickle pluss kampanjemodeller tilgjengelig på det tidspunktet, med en kvote på 200 forespørsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med høyere kvoter håndhevet over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende $12 per 5 timer, $30 per uke og $60 per måned (faktiske forespørselsantall varierer etter modell og bruk).", + "Gratis modeller inkluderer Big Pickle pluss kampanjemodeller tilgjengelig på det tidspunktet, med en kvote på 200 forespørsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med høyere kvoter håndhevet over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende $12 per 5 timer, $30 per uke og $60 per måned (faktiske forespørselsantall varierer etter modell og bruk).", "zen.api.error.rateLimitExceeded": "Rate limit overskredet. Vennligst prøv igjen senere.", "zen.api.error.modelNotSupported": "Modell {{model}} støttes ikke", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index f2219487b..554a2d0aa 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -252,7 +252,7 @@ export const dict = { "go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego", "go.meta.description": - "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.", + "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.", "go.hero.title": "Niskokosztowe modele do kodowania dla każdego", "go.hero.body": "Go udostępnia programowanie z agentami programistom na całym świecie. Oferuje hojne limity i niezawodny dostęp do najzdolniejszych modeli open source, dzięki czemu możesz budować za pomocą potężnych agentów, nie martwiąc się o koszty czy dostępność.", @@ -303,7 +303,7 @@ export const dict = { "go.problem.item2": "Hojne limity i niezawodny dostęp", "go.problem.item3": "Stworzony dla jak największej liczby programistów", "go.problem.item4": - "Zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7", + "Zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7", "go.how.title": "Jak działa Go", "go.how.body": "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc. Możesz go używać z OpenCode lub dowolnym agentem.", @@ -329,7 +329,7 @@ export const dict = { "go.faq.a2": "Go obejmuje poniższe modele z wysokimi limitami i niezawodnym dostępem.", "go.faq.q3": "Czy Go to to samo co Zen?", "go.faq.a3": - "Nie. Zen to model płatności za użycie, podczas gdy Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi limitami i niezawodnym dostępem do modeli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.", + "Nie. Zen to model płatności za użycie, podczas gdy Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi limitami i niezawodnym dostępem do modeli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.", "go.faq.q4": "Ile kosztuje Go?", "go.faq.a4.p1.beforePricing": "Go kosztuje", "go.faq.a4.p1.pricingLink": "$5 za pierwszy miesiąc", @@ -353,7 +353,7 @@ export const dict = { "go.faq.q9": "Jaka jest różnica między darmowymi modelami a Go?", "go.faq.a9": - "Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7 z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), w przybliżeniu równoważnymi $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie (rzeczywista liczba zapytań zależy od modelu i użycia).", + "Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7 z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), w przybliżeniu równoważnymi $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie (rzeczywista liczba zapytań zależy od modelu i użycia).", "zen.api.error.rateLimitExceeded": "Przekroczono limit zapytań. Spróbuj ponownie później.", "zen.api.error.modelNotSupported": "Model {{model}} nie jest obsługiwany", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 8fd76226e..1e5013419 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -255,7 +255,7 @@ export const dict = { "go.title": "OpenCode Go | Недорогие модели для кодинга для всех", "go.meta.description": - "Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.", + "Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.", "go.hero.title": "Недорогие модели для кодинга для всех", "go.hero.body": "Go открывает доступ к агентам-программистам разработчикам по всему миру. Предлагая щедрые лимиты и надежный доступ к наиболее способным моделям с открытым исходным кодом, вы можете создавать проекты с мощными агентами, не беспокоясь о затратах или доступности.", @@ -307,7 +307,7 @@ export const dict = { "go.problem.item2": "Щедрые лимиты и надежный доступ", "go.problem.item3": "Создан для максимального числа программистов", "go.problem.item4": - "Включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7", + "Включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7", "go.how.title": "Как работает Go", "go.how.body": "Go начинается с $5 за первый месяц, затем $10/месяц. Вы можете использовать его с OpenCode или любым агентом.", @@ -333,7 +333,7 @@ export const dict = { "go.faq.a2": "Go включает перечисленные ниже модели с щедрыми лимитами и надежным доступом.", "go.faq.q3": "Go — это то же самое, что и Zen?", "go.faq.a3": - "Нет. Zen - это оплата по мере использования, в то время как Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами и надежным доступом к моделям с открытым исходным кодом GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.", + "Нет. Zen - это оплата по мере использования, в то время как Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами и надежным доступом к моделям с открытым исходным кодом GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.", "go.faq.q4": "Сколько стоит Go?", "go.faq.a4.p1.beforePricing": "Go стоит", "go.faq.a4.p1.pricingLink": "$5 за первый месяц", @@ -357,7 +357,7 @@ export const dict = { "go.faq.q9": "В чем разница между бесплатными моделями и Go?", "go.faq.a9": - "Бесплатные модели включают Big Pickle плюс промо-модели, доступные на данный момент, с квотой 200 запросов/день. Go включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7 с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно $12 за 5 часов, $30 в неделю и $60 в месяц (фактическое количество запросов зависит от модели и использования).", + "Бесплатные модели включают Big Pickle плюс промо-модели, доступные на данный момент, с квотой 200 запросов/день. Go включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7 с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно $12 за 5 часов, $30 в неделю и $60 в месяц (фактическое количество запросов зависит от модели и использования).", "zen.api.error.rateLimitExceeded": "Превышен лимит запросов. Пожалуйста, попробуйте позже.", "zen.api.error.modelNotSupported": "Модель {{model}} не поддерживается", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index efe535094..3a2dc4ba4 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -250,7 +250,7 @@ export const dict = { "go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน", "go.meta.description": - "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7", + "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7", "go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน", "go.hero.body": "Go นำการเขียนโค้ดแบบเอเจนต์มาสู่นักเขียนโปรแกรมทั่วโลก เสนอขีดจำกัดที่กว้างขวางและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดได้อย่างน่าเชื่อถือ เพื่อให้คุณสามารถสร้างสรรค์ด้วยเอเจนต์ที่ทรงพลังโดยไม่ต้องกังวลเรื่องค่าใช้จ่ายหรือความพร้อมใช้งาน", @@ -300,7 +300,7 @@ export const dict = { "go.problem.item2": "ขีดจำกัดที่กว้างขวางและการเข้าถึงที่เชื่อถือได้", "go.problem.item3": "สร้างขึ้นเพื่อโปรแกรมเมอร์จำนวนมากที่สุดเท่าที่จะเป็นไปได้", "go.problem.item4": - "รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7", + "รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7", "go.how.title": "Go ทำงานอย่างไร", "go.how.body": "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน คุณสามารถใช้กับ OpenCode หรือเอเจนต์ใดก็ได้", "go.how.step1.title": "สร้างบัญชี", @@ -325,7 +325,7 @@ export const dict = { "go.faq.a2": "Go รวมโมเดลด้านล่างนี้ พร้อมขีดจำกัดที่มากและการเข้าถึงที่เชื่อถือได้", "go.faq.q3": "Go เหมือนกับ Zen หรือไม่?", "go.faq.a3": - "ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ในขณะที่ Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์ส GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 อย่างเชื่อถือได้", + "ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ในขณะที่ Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์ส GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 อย่างเชื่อถือได้", "go.faq.q4": "Go ราคาเท่าไหร่?", "go.faq.a4.p1.beforePricing": "Go ราคา", "go.faq.a4.p1.pricingLink": "$5 เดือนแรก", @@ -348,7 +348,7 @@ export const dict = { "go.faq.q9": "ความแตกต่างระหว่างโมเดลฟรีและ Go คืออะไร?", "go.faq.a9": - "โมเดลฟรีรวมถึง Big Pickle บวกกับโมเดลโปรโมชั่นที่มีให้ในขณะนั้น ด้วยโควต้า 200 คำขอ/วัน Go รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 ที่มีโควต้าคำขอสูงกว่า ซึ่งบังคับใช้ผ่านช่วงเวลาหมุนเวียน (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน (จำนวนคำขอจริงจะแตกต่างกันไปตามโมเดลและการใช้งาน)", + "โมเดลฟรีรวมถึง Big Pickle บวกกับโมเดลโปรโมชั่นที่มีให้ในขณะนั้น ด้วยโควต้า 200 คำขอ/วัน Go รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 ที่มีโควต้าคำขอสูงกว่า ซึ่งบังคับใช้ผ่านช่วงเวลาหมุนเวียน (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน (จำนวนคำขอจริงจะแตกต่างกันไปตามโมเดลและการใช้งาน)", "zen.api.error.rateLimitExceeded": "เกินขีดจำกัดอัตราการใช้งาน กรุณาลองใหม่ในภายหลัง", "zen.api.error.modelNotSupported": "ไม่รองรับโมเดล {{model}}", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index 114dcbdb0..e2370f83c 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri", "go.meta.description": - "Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 için cömert 5 saatlik istek limitleri sunar.", + "Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 için cömert 5 saatlik istek limitleri sunar.", "go.hero.title": "Herkes için düşük maliyetli kodlama modelleri", "go.hero.body": "Go, dünya çapındaki programcılara ajan tabanlı kodlama getiriyor. En yetenekli açık kaynaklı modellere cömert limitler ve güvenilir erişim sunarak, maliyet veya erişilebilirlik konusunda endişelenmeden güçlü ajanlarla geliştirme yapmanızı sağlar.", @@ -305,7 +305,7 @@ export const dict = { "go.problem.item2": "Cömert limitler ve güvenilir erişim", "go.problem.item3": "Mümkün olduğunca çok programcı için geliştirildi", "go.problem.item4": - "GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 içerir", + "GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 içerir", "go.how.title": "Go nasıl çalışır?", "go.how.body": "Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar. OpenCode veya herhangi bir ajanla kullanabilirsiniz.", @@ -331,7 +331,7 @@ export const dict = { "go.faq.a2": "Go, aşağıda listelenen modelleri cömert limitler ve güvenilir erişimle sunar.", "go.faq.q3": "Go, Zen ile aynı mı?", "go.faq.a3": - "Hayır. Zen kullandıkça öde modelidir, Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 açık kaynak modellerine cömert limitler ve güvenilir erişim sunar.", + "Hayır. Zen kullandıkça öde modelidir, Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 açık kaynak modellerine cömert limitler ve güvenilir erişim sunar.", "go.faq.q4": "Go ne kadar?", "go.faq.a4.p1.beforePricing": "Go'nun maliyeti", "go.faq.a4.p1.pricingLink": "İlk ay $5", @@ -355,7 +355,7 @@ export const dict = { "go.faq.q9": "Ücretsiz modeller ve Go arasındaki fark nedir?", "go.faq.a9": - "Ücretsiz modeller, günlük 200 istek kotası ile Big Pickle ve o sırada mevcut olan promosyonel modelleri içerir. Go ise GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 modellerini; yuvarlanan pencereler (5 saatlik, haftalık ve aylık) üzerinden uygulanan daha yüksek istek kotalarıyla içerir. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerine eşdeğerdir (gerçek istek sayıları modele ve kullanıma göre değişir).", + "Ücretsiz modeller, günlük 200 istek kotası ile Big Pickle ve o sırada mevcut olan promosyonel modelleri içerir. Go ise GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 modellerini; yuvarlanan pencereler (5 saatlik, haftalık ve aylık) üzerinden uygulanan daha yüksek istek kotalarıyla içerir. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerine eşdeğerdir (gerçek istek sayıları modele ve kullanıma göre değişir).", "zen.api.error.rateLimitExceeded": "İstek limiti aşıldı. Lütfen daha sonra tekrar deneyin.", "zen.api.error.modelNotSupported": "{{model}} modeli desteklenmiyor", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index 72a2a4570..649384c23 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -241,7 +241,7 @@ export const dict = { "go.title": "OpenCode Go | 人人可用的低成本编程模型", "go.meta.description": - "Go 首月 $5,之后 $10/月,提供对 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小时充裕请求额度。", + "Go 首月 $5,之后 $10/月,提供对 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小时充裕请求额度。", "go.hero.title": "人人可用的低成本编程模型", "go.hero.body": "Go 将代理编程带给全世界的程序员。提供充裕的限额和对最强大的开源模型的可靠访问,让您可以利用强大的代理进行构建,而无需担心成本或可用性。", @@ -291,7 +291,7 @@ export const dict = { "go.problem.item2": "充裕的限额和可靠的访问", "go.problem.item3": "为尽可能多的程序员打造", "go.problem.item4": - "包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7", + "包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7", "go.how.title": "Go 如何工作", "go.how.body": "Go 起价为首月 $5,之后 $10/月。您可以将其与 OpenCode 或任何代理搭配使用。", "go.how.step1.title": "创建账户", @@ -313,7 +313,7 @@ export const dict = { "go.faq.a2": "Go 包含下方列出的模型,提供充足的限额和可靠的访问。", "go.faq.q3": "Go 和 Zen 一样吗?", "go.faq.a3": - "不。Zen 是按量付费,而 Go 首月 $5,之后 $10/月,提供充裕的额度,并可可靠地访问 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等开源模型。", + "不。Zen 是按量付费,而 Go 首月 $5,之后 $10/月,提供充裕的额度,并可可靠地访问 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等开源模型。", "go.faq.q4": "Go 多少钱?", "go.faq.a4.p1.beforePricing": "Go 费用为", "go.faq.a4.p1.pricingLink": "首月 $5", @@ -335,7 +335,7 @@ export const dict = { "go.faq.q9": "免费模型和 Go 之间的区别是什么?", "go.faq.a9": - "免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60(实际请求计数因模型和使用情况而异)。", + "免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60(实际请求计数因模型和使用情况而异)。", "zen.api.error.rateLimitExceeded": "超出速率限制。请稍后重试。", "zen.api.error.modelNotSupported": "不支持模型 {{model}}", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index caea5c74b..8e93f989e 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -241,7 +241,7 @@ export const dict = { "go.title": "OpenCode Go | 低成本全民編碼模型", "go.meta.description": - "Go 首月 $5,之後 $10/月,提供對 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小時充裕請求額度。", + "Go 首月 $5,之後 $10/月,提供對 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小時充裕請求額度。", "go.hero.title": "低成本全民編碼模型", "go.hero.body": "Go 將代理編碼帶給全世界的程式設計師。提供寬裕的限額以及對最強大開源模型的穩定存取,讓你可以使用強大的代理進行構建,而無需擔心成本或可用性。", @@ -291,7 +291,7 @@ export const dict = { "go.problem.item2": "寬裕的限額與穩定存取", "go.problem.item3": "專為盡可能多的程式設計師打造", "go.problem.item4": - "包含 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7", + "包含 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7", "go.how.title": "Go 如何運作", "go.how.body": "Go 起價為首月 $5,之後 $10/月。您可以將其與 OpenCode 或任何代理搭配使用。", "go.how.step1.title": "建立帳號", @@ -313,7 +313,7 @@ export const dict = { "go.faq.a2": "Go 包含下方列出的模型,提供充足的額度與穩定的存取。", "go.faq.q3": "Go 與 Zen 一樣嗎?", "go.faq.a3": - "不。Zen 是按量付費,而 Go 首月 $5,之後 $10/月,提供充裕的額度,並可可靠地存取 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等開源模型。", + "不。Zen 是按量付費,而 Go 首月 $5,之後 $10/月,提供充裕的額度,並可可靠地存取 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等開源模型。", "go.faq.q4": "Go 費用是多少?", "go.faq.a4.p1.beforePricing": "Go 費用為", "go.faq.a4.p1.pricingLink": "首月 $5", @@ -335,7 +335,7 @@ export const dict = { "go.faq.q9": "免費模型與 Go 有什麼區別?", "go.faq.a9": - "免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為 200 次請求/天。Go 包括 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7,並在滾動視窗(5 小時、每週和每月)內執行更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60(實際請求數因模型和使用情況而異)。", + "免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為 200 次請求/天。Go 包括 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7,並在滾動視窗(5 小時、每週和每月)內執行更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60(實際請求數因模型和使用情況而異)。", "zen.api.error.rateLimitExceeded": "超出頻率限制。請稍後再試。", "zen.api.error.modelNotSupported": "不支援模型 {{model}}", diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index bae5ddd28..bfaf2969b 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -29,6 +29,8 @@ const models = [ { name: "Kimi K2.6", provider: "Moonshot AI" }, { name: "MiMo-V2-Pro", provider: "Xiaomi MiMo" }, { name: "MiMo-V2-Omni", provider: "Xiaomi MiMo" }, + { name: "MiMo-V2.5-Pro", provider: "Xiaomi MiMo" }, + { name: "MiMo-V2.5", provider: "Xiaomi MiMo" }, { name: "Qwen3.5 Plus", provider: "Alibaba Cloud Model Studio" }, { name: "Qwen3.6 Plus", provider: "Alibaba Cloud Model Studio" }, { name: "MiniMax M2.7", provider: "MiniMax" }, @@ -60,7 +62,7 @@ function LimitsGraph(props: { href: string }) { const graph = [ { id: "glm-5.1", name: "GLM-5.1", req: 880, d: "100ms" }, { id: "kimi-k2.6", name: "Kimi K2.6 (3x usage)", req: 3450, baseReq: 1150, d: "150ms" }, - { id: "mimo-v2-pro", name: "MiMo-V2-Pro", req: 1290, d: "150ms" }, + { id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", req: 1290, d: "150ms" }, { id: "qwen3.6-plus", name: "Qwen3.6 Plus", req: 3300, d: "280ms" }, { id: "minimax-m2.7", name: "MiniMax M2.7", req: 3400, d: "300ms" }, { id: "qwen3.5-plus", name: "Qwen3.5 Plus", req: 10200, d: "360ms" }, diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx index c894ace10..abd417e06 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx @@ -289,8 +289,10 @@ export function LiteSection() {
  • Kimi K2.6
  • GLM-5
  • GLM-5.1
  • -
  • Mimo-V2-Pro
  • -
  • Mimo-V2-Omni
  • +
  • MiMo-V2-Pro
  • +
  • MiMo-V2-Omni
  • +
  • MiMo-V2.5-Pro
  • +
  • MiMo-V2.5
  • MiniMax M2.5
  • MiniMax M2.7
  • Qwen3.5 Plus
  • diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 179999af8..008c83468 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -59,6 +59,8 @@ OpenCode Go حاليًا في المرحلة التجريبية. - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -86,8 +88,10 @@ OpenCode Go حاليًا في المرحلة التجريبية. | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -102,6 +106,8 @@ OpenCode Go حاليًا في المرحلة التجريبية. - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — ‏350 input، و41,000 cached، و250 output tokens لكل طلب - MiMo-V2-Omni — ‏1000 input، و60,000 cached، و140 output tokens لكل طلب +- MiMo-V2.5-Pro — ‏350 input، و41,000 cached، و250 output tokens لكل طلب +- MiMo-V2.5 — ‏1000 input، و60,000 cached، و140 output tokens لكل طلب يمكنك تتبّع استخدامك الحالي في **console**. @@ -131,6 +137,8 @@ OpenCode Go حاليًا في المرحلة التجريبية. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index b94a78ee7..ece9c0ca5 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -69,6 +69,8 @@ Trenutna lista modela uključuje: - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -96,8 +98,10 @@ Tabela ispod pruža procijenjeni broj zahtjeva na osnovu tipičnih obrazaca kori | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -112,6 +116,8 @@ Procjene se zasnivaju na zapaženim prosječnim obrascima zahtjeva: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 ulaznih, 41,000 keširanih, 250 izlaznih tokena po zahtjevu - MiMo-V2-Omni — 1000 ulaznih, 60,000 keširanih, 140 izlaznih tokena po zahtjevu +- MiMo-V2.5-Pro — 350 ulaznih, 41,000 keširanih, 250 izlaznih tokena po zahtjevu +- MiMo-V2.5 — 1000 ulaznih, 60,000 keširanih, 140 izlaznih tokena po zahtjevu Svoju trenutnu potrošnju možete pratiti u **konzoli**. @@ -143,6 +149,8 @@ Također možete pristupiti Go modelima putem sljedećih API endpointa. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 0ef5f1222..437e807ec 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -69,6 +69,8 @@ Den nuværende liste over modeller inkluderer: - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -96,8 +98,10 @@ Tabellen nedenfor giver et estimeret antal anmodninger baseret på typiske Go-fo | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -112,6 +116,8 @@ Estimaterne er baseret på observerede gennemsnitlige anmodningsmønstre: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 input, 41.000 cachelagrede, 250 output-tokens pr. anmodning - MiMo-V2-Omni — 1000 input, 60.000 cachelagrede, 140 output-tokens pr. anmodning +- MiMo-V2.5-Pro — 350 input, 41.000 cachelagrede, 250 output-tokens pr. anmodning +- MiMo-V2.5 — 1000 input, 60.000 cachelagrede, 140 output-tokens pr. anmodning Du kan spore dit nuværende forbrug i **konsollen**. @@ -143,6 +149,8 @@ Du kan også få adgang til Go-modeller gennem følgende API-endpoints. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index 269f6231e..8f60af1e9 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -61,6 +61,8 @@ Die aktuelle Liste der Modelle umfasst: - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -88,8 +90,10 @@ Die folgende Tabelle zeigt eine geschätzte Anzahl von Anfragen basierend auf ty | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -104,6 +108,8 @@ Die Schätzungen basieren auf beobachteten durchschnittlichen Anfragemustern: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 Input-, 41.000 Cached-, 250 Output-Tokens pro Anfrage - MiMo-V2-Omni — 1.000 Input-, 60.000 Cached-, 140 Output-Tokens pro Anfrage +- MiMo-V2.5-Pro — 350 Input-, 41.000 Cached-, 250 Output-Tokens pro Anfrage +- MiMo-V2.5 — 1.000 Input-, 60.000 Cached-, 140 Output-Tokens pro Anfrage Du kannst deine aktuelle Nutzung in der **Console** verfolgen. @@ -133,6 +139,8 @@ Du kannst auf die Go-Modelle auch über die folgenden API-Endpunkte zugreifen. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index e70e3dd1f..1d4cf709c 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -69,6 +69,8 @@ La lista actual de modelos incluye: - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -96,8 +98,10 @@ La siguiente tabla proporciona una cantidad estimada de peticiones basada en los | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -112,6 +116,8 @@ Las estimaciones se basan en los patrones de peticiones promedio observados: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 tokens de entrada, 41,000 en caché, 250 tokens de salida por petición - MiMo-V2-Omni — 1000 tokens de entrada, 60,000 en caché, 140 tokens de salida por petición +- MiMo-V2.5-Pro — 350 tokens de entrada, 41,000 en caché, 250 tokens de salida por petición +- MiMo-V2.5 — 1000 tokens de entrada, 60,000 en caché, 140 tokens de salida por petición Puedes realizar un seguimiento de tu uso actual en la **consola**. @@ -143,6 +149,8 @@ También puedes acceder a los modelos de Go a través de los siguientes endpoint | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 5527d9d86..b288ff1bf 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -59,6 +59,8 @@ La liste actuelle des modèles comprend : - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -86,8 +88,10 @@ Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur d | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -102,6 +106,8 @@ Les estimations sont basées sur les modèles de requêtes moyens observés : - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 tokens en entrée, 41,000 en cache, 250 tokens en sortie par requête - MiMo-V2-Omni — 1000 tokens en entrée, 60,000 en cache, 140 tokens en sortie par requête +- MiMo-V2.5-Pro — 350 tokens en entrée, 41,000 en cache, 250 tokens en sortie par requête +- MiMo-V2.5 — 1000 tokens en entrée, 60,000 en cache, 140 tokens en sortie par requête Vous pouvez suivre votre utilisation actuelle dans la **console**. @@ -131,6 +137,8 @@ Vous pouvez également accéder aux modèles Go via les points de terminaison d' | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index a39b6f7d2..cd3e2c844 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -69,6 +69,8 @@ The current list of models includes: - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **MiniMax M2.7** - **Qwen3.5 Plus** @@ -96,8 +98,10 @@ The table below provides an estimated request count based on typical Go usage pa | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -110,6 +114,8 @@ Estimates are based on observed average request patterns: - MiniMax M2.7/M2.5 — 300 input, 55,000 cached, 125 output tokens per request - MiMo-V2-Pro — 350 input, 41,000 cached, 250 output tokens per request - MiMo-V2-Omni — 1000 input, 60,000 cached, 140 output tokens per request +- MiMo-V2.5-Pro — 350 input, 41,000 cached, 250 output tokens per request +- MiMo-V2.5 — 1000 input, 60,000 cached, 140 output tokens per request - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -143,6 +149,8 @@ You can also access Go models through the following API endpoints. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index 6cdf7ac6c..9cf04d77d 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -67,6 +67,8 @@ L'elenco attuale dei modelli include: - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -94,8 +96,10 @@ La tabella seguente fornisce una stima del conteggio delle richieste in base a p | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -110,6 +114,8 @@ Le stime si basano sui pattern medi di richieste osservati: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 di input, 41.000 in cache, 250 token di output per richiesta - MiMo-V2-Omni — 1000 di input, 60.000 in cache, 140 token di output per richiesta +- MiMo-V2.5-Pro — 350 di input, 41.000 in cache, 250 token di output per richiesta +- MiMo-V2.5 — 1000 di input, 60.000 in cache, 140 token di output per richiesta Puoi monitorare il tuo utilizzo attuale nella **console**. @@ -141,6 +147,8 @@ Puoi anche accedere ai modelli Go tramite i seguenti endpoint API. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index f122d2367..40c1dbf36 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -59,6 +59,8 @@ OpenCode Goをサブスクライブできるのは、1つのワークスペー - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -86,8 +88,10 @@ OpenCode Goには以下の制限が含まれています: | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -102,6 +106,8 @@ OpenCode Goには以下の制限が含まれています: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — リクエストあたり 入力 350トークン、キャッシュ 41,000トークン、出力 250トークン - MiMo-V2-Omni — リクエストあたり 入力 1000トークン、キャッシュ 60,000トークン、出力 140トークン +- MiMo-V2.5-Pro — リクエストあたり 入力 350トークン、キャッシュ 41,000トークン、出力 250トークン +- MiMo-V2.5 — リクエストあたり 入力 1000トークン、キャッシュ 60,000トークン、出力 140トークン 現在の利用状況は**コンソール**で追跡できます。 @@ -131,6 +137,8 @@ Zen残高にクレジットがある場合は、コンソールで**Use balance* | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index cd0b1b8da..4b3dbd27d 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -59,6 +59,8 @@ workspace당 한 명의 멤버만 OpenCode Go를 구독할 수 있습니다. - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -86,8 +88,10 @@ OpenCode Go에는 다음과 같은 한도가 포함됩니다. | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -102,6 +106,8 @@ OpenCode Go에는 다음과 같은 한도가 포함됩니다. - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 요청당 입력 350, 캐시 41,000, 출력 토큰 250 - MiMo-V2-Omni — 요청당 입력 1000, 캐시 60,000, 출력 토큰 140 +- MiMo-V2.5-Pro — 요청당 입력 350, 캐시 41,000, 출력 토큰 250 +- MiMo-V2.5 — 요청당 입력 1000, 캐시 60,000, 출력 토큰 140 현재 사용량은 **console**에서 확인할 수 있습니다. @@ -131,6 +137,8 @@ Zen 잔액에 크레딧도 있다면, console에서 **Use balance** 옵션을 | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index 776cc0c92..f062683e8 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -69,6 +69,8 @@ Den nåværende listen over modeller inkluderer: - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -96,8 +98,10 @@ Tabellen nedenfor gir et estimert antall forespørsler basert på typiske bruksm | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -112,6 +116,8 @@ Estimatene er basert på observerte gjennomsnittlige forespørselsmønstre: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 input, 41 000 bufret, 250 output-tokens per forespørsel - MiMo-V2-Omni — 1000 input, 60 000 bufret, 140 output-tokens per forespørsel +- MiMo-V2.5-Pro — 350 input, 41 000 bufret, 250 output-tokens per forespørsel +- MiMo-V2.5 — 1000 input, 60 000 bufret, 140 output-tokens per forespørsel Du kan spore din nåværende bruk i **konsollen**. @@ -143,6 +149,8 @@ Du kan også få tilgang til Go-modeller gjennom følgende API-endepunkter. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index d99f5e098..12abb29bc 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -63,6 +63,8 @@ Obecna lista modeli obejmuje: - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -90,8 +92,10 @@ Poniższa tabela przedstawia szacunkową liczbę żądań na podstawie typowych | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -106,6 +110,8 @@ Szacunki opierają się na zaobserwowanych średnich wzorcach żądań: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 tokenów wejściowych, 41 000 w pamięci podręcznej, 250 tokenów wyjściowych na żądanie - MiMo-V2-Omni — 1000 tokenów wejściowych, 60 000 w pamięci podręcznej, 140 tokenów wyjściowych na żądanie +- MiMo-V2.5-Pro — 350 tokenów wejściowych, 41 000 w pamięci podręcznej, 250 tokenów wyjściowych na żądanie +- MiMo-V2.5 — 1000 tokenów wejściowych, 60 000 w pamięci podręcznej, 140 tokenów wyjściowych na żądanie Możesz śledzić swoje bieżące zużycie w **konsoli**. @@ -135,6 +141,8 @@ Możesz również uzyskać dostęp do modeli Go za pośrednictwem następującyc | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index 631038298..b41cb0d0e 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -69,6 +69,8 @@ A lista atual de modelos inclui: - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -96,8 +98,10 @@ A tabela abaixo fornece uma contagem estimada de requisições com base nos padr | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -112,6 +116,8 @@ As estimativas baseiam-se nos padrões médios de requisições observados: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 tokens de entrada, 41.000 em cache, 250 tokens de saída por requisição - MiMo-V2-Omni — 1000 tokens de entrada, 60.000 em cache, 140 tokens de saída por requisição +- MiMo-V2.5-Pro — 350 tokens de entrada, 41.000 em cache, 250 tokens de saída por requisição +- MiMo-V2.5 — 1000 tokens de entrada, 60.000 em cache, 140 tokens de saída por requisição Você pode acompanhar o seu uso atual no **console**. @@ -143,6 +149,8 @@ Você também pode acessar os modelos do Go através dos seguintes endpoints de | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index 60f01c2b5..62ce99ef9 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -69,6 +69,8 @@ OpenCode Go работает так же, как и любой другой пр - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -96,8 +98,10 @@ OpenCode Go включает следующие лимиты: | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -112,6 +116,8 @@ OpenCode Go включает следующие лимиты: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 входных, 41,000 кешированных, 250 выходных токенов на запрос - MiMo-V2-Omni — 1000 входных, 60,000 кешированных, 140 выходных токенов на запрос +- MiMo-V2.5-Pro — 350 входных, 41,000 кешированных, 250 выходных токенов на запрос +- MiMo-V2.5 — 1000 входных, 60,000 кешированных, 140 выходных токенов на запрос Вы можете отслеживать текущее использование в **консоли**. @@ -143,6 +149,8 @@ OpenCode Go включает следующие лимиты: | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index 3af1eadc9..84ce3546c 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -59,6 +59,8 @@ OpenCode Go ทำงานเหมือนกับผู้ให้บร - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -86,8 +88,10 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -102,6 +106,8 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 input, 41,000 cached, 250 output tokens ต่อ request - MiMo-V2-Omni — 1000 input, 60,000 cached, 140 output tokens ต่อ request +- MiMo-V2.5-Pro — 350 input, 41,000 cached, 250 output tokens ต่อ request +- MiMo-V2.5 — 1000 input, 60,000 cached, 140 output tokens ต่อ request คุณสามารถติดตามการใช้งานปัจจุบันของคุณได้ใน **console** @@ -131,6 +137,8 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index e962c0680..edd102685 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -59,6 +59,8 @@ Mevcut model listesi şunları içerir: - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -86,8 +88,10 @@ Aşağıdaki tablo, tipik Go kullanım modellerine dayalı tahmini bir istek say | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -102,6 +106,8 @@ Tahminler, gözlemlenen ortalama istek modellerine dayanmaktadır: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — İstek başına 350 girdi, 41.000 önbelleğe alınmış, 250 çıktı token'ı - MiMo-V2-Omni — İstek başına 1000 girdi, 60.000 önbelleğe alınmış, 140 çıktı token'ı +- MiMo-V2.5-Pro — İstek başına 350 girdi, 41.000 önbelleğe alınmış, 250 çıktı token'ı +- MiMo-V2.5 — İstek başına 1000 girdi, 60.000 önbelleğe alınmış, 140 çıktı token'ı Mevcut kullanımınızı **konsoldan** takip edebilirsiniz. @@ -131,6 +137,8 @@ Go modellerine aşağıdaki API uç noktaları aracılığıyla da erişebilirsi | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index ac3b5f9bf..0c1cf9827 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -59,6 +59,8 @@ OpenCode Go 的工作方式与 OpenCode 中的其他提供商一样。 - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -86,8 +88,10 @@ OpenCode Go 包含以下限制: | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -99,6 +103,8 @@ OpenCode Go 包含以下限制: - Kimi K2.5/K2.6 — 每次请求 870 个输入 token,55,000 个缓存 token,200 个输出 token - MiMo-V2-Pro — 每次请求 350 个输入 token,41,000 个缓存 token,250 个输出 token - MiMo-V2-Omni — 每次请求 1000 个输入 token,60,000 个缓存 token,140 个输出 token +- MiMo-V2.5-Pro — 每次请求 350 个输入 token,41,000 个缓存 token,250 个输出 token +- MiMo-V2.5 — 每次请求 1000 个输入 token,60,000 个缓存 token,140 个输出 token - MiniMax M2.7/M2.5 — 每次请求 300 个输入 token,55,000 个缓存 token,125 个输出 token - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -131,6 +137,8 @@ OpenCode Go 包含以下限制: | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index 0621a6694..c1016028b 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -59,6 +59,8 @@ OpenCode Go 的運作方式與 OpenCode 中的任何其他供應商相同。 - **Kimi K2.6** - **MiMo-V2-Pro** - **MiMo-V2-Omni** +- **MiMo-V2.5-Pro** +- **MiMo-V2.5** - **MiniMax M2.5** - **Qwen3.5 Plus** - **Qwen3.6 Plus** @@ -86,8 +88,10 @@ OpenCode Go 包含以下限制: | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | @@ -102,6 +106,8 @@ OpenCode Go 包含以下限制: - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 每次請求 350 個輸入 token、41,000 個快取 token、250 個輸出 token - MiMo-V2-Omni — 每次請求 1000 個輸入 token、60,000 個快取 token、140 個輸出 token +- MiMo-V2.5-Pro — 每次請求 350 個輸入 token、41,000 個快取 token、250 個輸出 token +- MiMo-V2.5 — 每次請求 1000 個輸入 token、60,000 個快取 token、140 個輸出 token 您可以在 **console** 中追蹤您目前的使用量。 @@ -131,6 +137,8 @@ OpenCode Go 包含以下限制: | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | From 5d133f2785aada81d65b14e3f86f14063900596c Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 22 Apr 2026 16:29:44 +0000 Subject: [PATCH 063/426] chore: generate --- packages/web/src/content/docs/ar/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/bs/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/da/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/de/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/es/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/fr/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/it/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/ja/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/ko/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/nb/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/pl/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/pt-br/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/ru/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/th/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/tr/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/zh-cn/go.mdx | 54 +++++++++++----------- packages/web/src/content/docs/zh-tw/go.mdx | 54 +++++++++++----------- 18 files changed, 486 insertions(+), 486 deletions(-) diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 008c83468..785ea35b6 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -82,20 +82,20 @@ OpenCode Go حاليًا في المرحلة التجريبية. يوضح الجدول أدناه عددًا تقديريًا للطلبات بناءً على أنماط استخدام Go المعتادة: -| Model | الطلبات لكل 5 ساعات | الطلبات في الأسبوع | الطلبات في الشهر | -| ------------ | ------------------- | ------------------ | ---------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | الطلبات لكل 5 ساعات | الطلبات في الأسبوع | الطلبات في الشهر | +| ------------- | ------------------- | ------------------ | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | تستند التقديرات إلى متوسطات أنماط الطلبات المرصودة: @@ -129,20 +129,20 @@ OpenCode Go حاليًا في المرحلة التجريبية. يمكنك أيضًا الوصول إلى نماذج Go عبر نقاط نهاية API التالية. -| Model | Model ID | Endpoint | AI SDK Package | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Model | Model ID | Endpoint | AI SDK Package | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | يستخدم [model id](/docs/config/#models) في إعدادات OpenCode لديك التنسيق `opencode-go/`. على سبيل المثال، بالنسبة إلى Kimi K2.6، ستستخدم `opencode-go/kimi-k2.6` في إعداداتك. diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index ece9c0ca5..523f1ef8e 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -92,20 +92,20 @@ Ograničenja su definisana u dolarskoj vrijednosti. To znači da vaš stvarni br Tabela ispod pruža procijenjeni broj zahtjeva na osnovu tipičnih obrazaca korištenja Go pretplate: -| Model | zahtjeva na 5 sati | zahtjeva sedmično | zahtjeva mjesečno | -| ------------ | ------------------ | ----------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | zahtjeva na 5 sati | zahtjeva sedmično | zahtjeva mjesečno | +| ------------- | ------------------ | ----------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Procjene se zasnivaju na zapaženim prosječnim obrascima zahtjeva: @@ -141,20 +141,20 @@ nakon što dostignete ograničenja upotrebe umjesto blokiranja zahtjeva. Također možete pristupiti Go modelima putem sljedećih API endpointa. -| Model | Model ID | Endpoint | AI SDK Paket | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Model | Model ID | Endpoint | AI SDK Paket | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [Model id](/docs/config/#models) u vašoj OpenCode konfiguraciji koristi format `opencode-go/`. Na primjer, za Kimi K2.6, koristili biste diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 437e807ec..86a834b98 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -92,20 +92,20 @@ Grænserne er defineret i dollarværdi. Det betyder, at dit faktiske antal anmod Tabellen nedenfor giver et estimeret antal anmodninger baseret på typiske Go-forbrugsmønstre: -| Model | anmodninger pr. 5 timer | anmodninger pr. uge | anmodninger pr. måned | -| ------------ | ----------------------- | ------------------- | --------------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | anmodninger pr. 5 timer | anmodninger pr. uge | anmodninger pr. måned | +| ------------- | ----------------------- | ------------------- | --------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Estimaterne er baseret på observerede gennemsnitlige anmodningsmønstre: @@ -141,20 +141,20 @@ når du har nået dine forbrugsgrænser, i stedet for at blokere anmodninger. Du kan også få adgang til Go-modeller gennem følgende API-endpoints. -| Model | Model ID | Endpoint | AI SDK Package | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Model | Model ID | Endpoint | AI SDK Package | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Dit [model id](/docs/config/#models) i din OpenCode config bruger formatet `opencode-go/`. For eksempel for Kimi K2.6, vil du diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index 8f60af1e9..49c0efda5 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -84,20 +84,20 @@ Limits sind in Dollarwerten definiert. Das bedeutet, dass die tatsächliche Anza Die folgende Tabelle zeigt eine geschätzte Anzahl von Anfragen basierend auf typischen Go-Nutzungsmustern: -| Model | Anfragen pro 5 Stunden | Anfragen pro Woche | Anfragen pro Monat | -| ------------ | ---------------------- | ------------------ | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | Anfragen pro 5 Stunden | Anfragen pro Woche | Anfragen pro Monat | +| ------------- | ---------------------- | ------------------ | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Die Schätzungen basieren auf beobachteten durchschnittlichen Anfragemustern: @@ -131,20 +131,20 @@ Wenn du auch Guthaben auf deinem Zen-Konto hast, kannst du in der Console die Op Du kannst auf die Go-Modelle auch über die folgenden API-Endpunkte zugreifen. -| Modell | Modell-ID | Endpunkt | AI SDK Package | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Modell | Modell-ID | Endpunkt | AI SDK Package | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Die [Modell-ID](/docs/config/#models) in deiner OpenCode Config verwendet das Format `opencode-go/`. Für Kimi K2.6 würdest du beispielsweise `opencode-go/kimi-k2.6` in deiner Config verwenden. diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index 1d4cf709c..a541171ca 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -92,20 +92,20 @@ Los límites se definen en valor en dólares. Esto significa que tu cantidad rea La siguiente tabla proporciona una cantidad estimada de peticiones basada en los patrones típicos de uso de Go: -| Model | peticiones por 5 horas | peticiones por semana | peticiones por mes | -| ------------ | ---------------------- | --------------------- | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | peticiones por 5 horas | peticiones por semana | peticiones por mes | +| ------------- | ---------------------- | --------------------- | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Las estimaciones se basan en los patrones de peticiones promedio observados: @@ -141,20 +141,20 @@ después de que hayas alcanzado tus límites de uso en lugar de bloquear las pet También puedes acceder a los modelos de Go a través de los siguientes endpoints de la API. -| Modelo | ID del modelo | Endpoint | Paquete de AI SDK | -| ------------ | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Modelo | ID del modelo | Endpoint | Paquete de AI SDK | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | El [ID del modelo](/docs/config/#models) en tu configuración de OpenCode usa el formato `opencode-go/`. Por ejemplo, para Kimi K2.6, usarías diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index b288ff1bf..5f55128ed 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -82,20 +82,20 @@ Les limites sont définies en valeur monétaire (dollars). Cela signifie que vot Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur des modèles d'utilisation typiques de Go : -| Model | requêtes par 5 heures | requêtes par semaine | requêtes par mois | -| ------------ | --------------------- | -------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | requêtes par 5 heures | requêtes par semaine | requêtes par mois | +| ------------- | --------------------- | -------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Les estimations sont basées sur les modèles de requêtes moyens observés : @@ -129,20 +129,20 @@ Si vous avez également des crédits sur votre solde Zen, vous pouvez activer l' Vous pouvez également accéder aux modèles Go via les points de terminaison d'API suivants. -| Modèle | ID de modèle | Point de terminaison | Package AI SDK | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Modèle | ID de modèle | Point de terminaison | Package AI SDK | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | L'[ID de modèle](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode-go/`. Par exemple, pour Kimi K2.6, vous utiliseriez `opencode-go/kimi-k2.6` dans votre configuration. diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index cd3e2c844..946c70de3 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -92,20 +92,20 @@ Limits are defined in dollar value. This means your actual request count depends The table below provides an estimated request count based on typical Go usage patterns: -| Model | requests per 5 hour | requests per week | requests per month | -| ------------ | ------------------- | ----------------- | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | requests per 5 hour | requests per week | requests per month | +| ------------- | ------------------- | ----------------- | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Estimates are based on observed average request patterns: @@ -141,20 +141,20 @@ after you've reached your usage limits instead of blocking requests. You can also access Go models through the following API endpoints. -| Model | Model ID | Endpoint | AI SDK Package | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Model | Model ID | Endpoint | AI SDK Package | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | The [model id](/docs/config/#models) in your OpenCode config uses the format `opencode-go/`. For example, for Kimi K2.6, you would diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index 9cf04d77d..341a22c4c 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -90,20 +90,20 @@ I limiti sono definiti in valore in dollari. Questo significa che il conteggio e La tabella seguente fornisce una stima del conteggio delle richieste in base a pattern di utilizzo tipici di Go: -| Model | richieste ogni 5 ore | richieste a settimana | richieste al mese | -| ------------ | -------------------- | --------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | richieste ogni 5 ore | richieste a settimana | richieste al mese | +| ------------- | -------------------- | --------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Le stime si basano sui pattern medi di richieste osservati: @@ -139,20 +139,20 @@ dopo che avrai raggiunto i limiti di utilizzo invece di bloccare le richieste. Puoi anche accedere ai modelli Go tramite i seguenti endpoint API. -| Modello | ID Modello | Endpoint | Pacchetto AI SDK | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Modello | ID Modello | Endpoint | Pacchetto AI SDK | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Il [model id](/docs/config/#models) nella tua OpenCode config utilizza il formato `opencode-go/`. Ad esempio, per Kimi K2.6, useresti diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index 40c1dbf36..ddd5a6680 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -82,20 +82,20 @@ OpenCode Goには以下の制限が含まれています: 以下の表は、一般的なGoの利用パターンに基づいた推定リクエスト数を示しています: -| Model | 5時間あたりのリクエスト数 | 週間リクエスト数 | 月間リクエスト数 | -| ------------ | ------------------------- | ---------------- | ---------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | 5時間あたりのリクエスト数 | 週間リクエスト数 | 月間リクエスト数 | +| ------------- | ------------------------- | ---------------- | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | 推定値は、観測された平均的なリクエストパターンに基づいています: @@ -129,20 +129,20 @@ Zen残高にクレジットがある場合は、コンソールで**Use balance* 以下のAPIエンドポイントを通じて、Goモデルにアクセスすることもできます。 -| Model | Model ID | Endpoint | AI SDK Package | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Model | Model ID | Endpoint | AI SDK Package | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode設定の[model id](/docs/config/#models)は、`opencode-go/`という形式を使用します。たとえば、Kimi K2.6の場合は、設定で`opencode-go/kimi-k2.6`を使用します。 diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index 4b3dbd27d..da787040f 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -82,20 +82,20 @@ OpenCode Go에는 다음과 같은 한도가 포함됩니다. 아래 표는 일반적인 Go 사용 패턴을 기준으로 한 예상 요청 횟수를 보여줍니다. -| Model | 5시간당 요청 횟수 | 주간 요청 횟수 | 월간 요청 횟수 | -| ------------ | ----------------- | -------------- | -------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | 5시간당 요청 횟수 | 주간 요청 횟수 | 월간 요청 횟수 | +| ------------- | ----------------- | -------------- | -------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | 예상치는 관찰된 평균 요청 패턴을 기준으로 합니다. @@ -129,20 +129,20 @@ Zen 잔액에 크레딧도 있다면, console에서 **Use balance** 옵션을 다음 API 엔드포인트를 통해서도 Go 모델에 액세스할 수 있습니다. -| 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode config의 [model id](/docs/config/#models)는 `opencode-go/` 형식을 사용합니다. 예를 들어 Kimi K2.6의 경우 config에서 `opencode-go/kimi-k2.6`를 사용하면 됩니다. diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index f062683e8..95c05417c 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -92,20 +92,20 @@ Grensene er definert i dollarverdi. Dette betyr at ditt faktiske antall forespø Tabellen nedenfor gir et estimert antall forespørsler basert på typiske bruksmønstre for Go: -| Model | forespørsler per 5 timer | forespørsler per uke | forespørsler per måned | -| ------------ | ------------------------ | -------------------- | ---------------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | forespørsler per 5 timer | forespørsler per uke | forespørsler per måned | +| ------------- | ------------------------ | -------------------- | ---------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Estimatene er basert på observerte gjennomsnittlige forespørselsmønstre: @@ -141,20 +141,20 @@ etter at du har nådd bruksgrensene dine, i stedet for å blokkere forespørsler Du kan også få tilgang til Go-modeller gjennom følgende API-endepunkter. -| Modell | Modell-ID | Endepunkt | AI SDK Package | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Modell | Modell-ID | Endepunkt | AI SDK Package | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [Modell-ID-en](/docs/config/#models) i din OpenCode-konfigurasjon bruker formatet `opencode-go/`. For eksempel, for Kimi K2.6, vil du diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index 12abb29bc..9ae3ea34b 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -86,20 +86,20 @@ Limity są zdefiniowane w wartości w dolarach. Oznacza to, że rzeczywista licz Poniższa tabela przedstawia szacunkową liczbę żądań na podstawie typowych wzorców korzystania z Go: -| Model | żądania na 5 godzin | żądania na tydzień | żądania na miesiąc | -| ------------ | ------------------- | ------------------ | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | żądania na 5 godzin | żądania na tydzień | żądania na miesiąc | +| ------------- | ------------------- | ------------------ | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Szacunki opierają się na zaobserwowanych średnich wzorcach żądań: @@ -133,20 +133,20 @@ Jeśli masz również środki na swoim saldzie Zen, możesz włączyć opcję ** Możesz również uzyskać dostęp do modeli Go za pośrednictwem następujących punktów końcowych API. -| Model | ID modelu | Punkt końcowy | Pakiet AI SDK | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Model | ID modelu | Punkt końcowy | Pakiet AI SDK | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [ID modelu](/docs/config/#models) w Twojej konfiguracji OpenCode używa formatu `opencode-go/`. Na przykład dla Kimi K2.6 należy użyć diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index b41cb0d0e..7d4d90ed5 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -92,20 +92,20 @@ Os limites são definidos em valor em dólares. Isso significa que a sua contage A tabela abaixo fornece uma contagem estimada de requisições com base nos padrões típicos de uso do Go: -| Model | requisições por 5 horas | requisições por semana | requisições por mês | -| ------------ | ----------------------- | ---------------------- | ------------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | requisições por 5 horas | requisições por semana | requisições por mês | +| ------------- | ----------------------- | ---------------------- | ------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | As estimativas baseiam-se nos padrões médios de requisições observados: @@ -141,20 +141,20 @@ após você atingir os seus limites de uso em vez de bloquear as requisições. Você também pode acessar os modelos do Go através dos seguintes endpoints de API. -| Modelo | ID do Modelo | Endpoint | Pacote do AI SDK | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Modelo | ID do Modelo | Endpoint | Pacote do AI SDK | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | O [ID do modelo](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode-go/`. Por exemplo, para o Kimi K2.6, você usaria diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index 62ce99ef9..a8d33f296 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -92,20 +92,20 @@ OpenCode Go включает следующие лимиты: В таблице ниже приведено примерное количество запросов на основе типичных сценариев использования Go: -| Model | запросов за 5 часов | запросов в неделю | запросов в месяц | -| ------------ | ------------------- | ----------------- | ---------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | запросов за 5 часов | запросов в неделю | запросов в месяц | +| ------------- | ------------------- | ----------------- | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Оценки основаны на наблюдаемых средних показателях запросов: @@ -141,20 +141,20 @@ OpenCode Go включает следующие лимиты: Вы также можете получить доступ к моделям Go через следующие API-эндпоинты. -| Модель | ID модели | Эндпоинт | Пакет AI SDK | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Модель | ID модели | Эндпоинт | Пакет AI SDK | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [ID модели](/docs/config/#models) в вашем конфиге OpenCode использует формат `opencode-go/`. Например, для Kimi K2.6 вам нужно diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index 84ce3546c..fb0262c95 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -82,20 +82,20 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: ตารางด้านล่างแสดงจำนวน request โดยประมาณตามรูปแบบการใช้งานปกติของ Go: -| Model | requests ต่อ 5 ชั่วโมง | requests ต่อสัปดาห์ | requests ต่อเดือน | -| ------------ | ---------------------- | ------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | requests ต่อ 5 ชั่วโมง | requests ต่อสัปดาห์ | requests ต่อเดือน | +| ------------- | ---------------------- | ------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | การประมาณการอ้างอิงจากรูปแบบการใช้งาน request โดยเฉลี่ยที่สังเกตพบ: @@ -129,20 +129,20 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: คุณสามารถเข้าถึงโมเดลของ Go ผ่าน API endpoints ต่อไปนี้ได้เช่นกัน -| Model | Model ID | Endpoint | AI SDK Package | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Model | Model ID | Endpoint | AI SDK Package | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [model id](/docs/config/#models) ใน OpenCode config ของคุณจะใช้รูปแบบ `opencode-go/` ตัวอย่างเช่น สำหรับ Kimi K2.6 คุณจะใช้ `opencode-go/kimi-k2.6` ใน config ของคุณ diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index edd102685..96a1ca3e2 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -82,20 +82,20 @@ Limitler dolar değeri üzerinden belirlenmiştir. Bu, gerçek istek sayınızı Aşağıdaki tablo, tipik Go kullanım modellerine dayalı tahmini bir istek sayısı sunmaktadır: -| Model | 5 saatte bir istek | haftalık istek | aylık istek | -| ------------ | ------------------ | -------------- | ----------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | 5 saatte bir istek | haftalık istek | aylık istek | +| ------------- | ------------------ | -------------- | ----------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Tahminler, gözlemlenen ortalama istek modellerine dayanmaktadır: @@ -129,20 +129,20 @@ Eğer Zen bakiyenizde kredileriniz varsa, konsoldan **Bakiye kullan (Use balance Go modellerine aşağıdaki API uç noktaları aracılığıyla da erişebilirsiniz. -| Model | Model ID | Uç Nokta | AI SDK Paketi | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Model | Model ID | Uç Nokta | AI SDK Paketi | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode yapılandırmanızdaki [model id](/docs/config/#models) formatı `opencode-go/` şeklindedir. Örneğin, Kimi K2.6 için yapılandırmanızda `opencode-go/kimi-k2.6` kullanmalısınız. diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index 0c1cf9827..f52f5b572 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -82,20 +82,20 @@ OpenCode Go 包含以下限制: 下表提供了基于典型 Go 使用模式的预估请求数: -| Model | 每 5 小时请求数 | 每周请求数 | 每月请求数 | -| ------------ | --------------- | ---------- | ---------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | 每 5 小时请求数 | 每周请求数 | 每月请求数 | +| ------------- | --------------- | ---------- | ---------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | 预估值基于观察到的平均请求模式: @@ -129,20 +129,20 @@ OpenCode Go 包含以下限制: 你也可以通过以下 API 端点访问 Go 模型。 -| 模型 | 模型 ID | 端点 | AI SDK 包 | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 模型 | 模型 ID | 端点 | AI SDK 包 | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | 你 OpenCode 配置中的 [模型 ID](/docs/config/#models) 使用 `opencode-go/` 格式。例如,对于 Kimi K2.6,你将在配置中使用 `opencode-go/kimi-k2.6`。 diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index c1016028b..481c08cec 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -82,20 +82,20 @@ OpenCode Go 包含以下限制: 下表提供了基於典型 Go 使用模式的預估請求次數: -| Model | 每 5 小時請求數 | 每週請求數 | 每月請求數 | -| ------------ | --------------- | ---------- | ---------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | 每 5 小時請求數 | 每週請求數 | 每月請求數 | +| ------------- | --------------- | ---------- | ---------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | 預估值是基於觀察到的平均請求模式: @@ -129,20 +129,20 @@ OpenCode Go 包含以下限制: 您也可以透過以下 API 端點存取 Go 模型。 -| 模型 | 模型 ID | 端點 | AI SDK 套件 | -| ------------ | ------------ | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| 模型 | 模型 ID | 端點 | AI SDK 套件 | +| ------------- | ------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | 您的 OpenCode 設定中的 [model id](/docs/config/#models) 使用 `opencode-go/` 格式。例如,Kimi K2.6 在設定中應使用 `opencode-go/kimi-k2.6`。 From 58db41b4b9fac2bfcf1f935cc114b3e4a069eade Mon Sep 17 00:00:00 2001 From: Caleb Norton Date: Wed, 22 Apr 2026 17:47:59 -0500 Subject: [PATCH 064/426] chore: update nix bun version (#23881) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 805be8739..1c8e62bd8 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1773909469, - "narHash": "sha256-vglVrLfHjFIzIdV9A27Ugul6rh3I1qHbbitGW7dk420=", + "lastModified": 1776683584, + "narHash": "sha256-NuTLMrr10Tng72hurYG8jYQ4XKK8wnpJmOGcPiis96g=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7149c06513f335be57f26fcbbbe34afda923882b", + "rev": "9dd5558b06dbdacbf635a3dd36dce1b1a7ee3a89", "type": "github" }, "original": { From e383df4b17eecd6e6718e43c17438aa2eb818ee9 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Thu, 23 Apr 2026 09:24:11 +1000 Subject: [PATCH 065/426] feat: support pull diagnostics in the LSP client (C#, Kotlin, etc) (#23771) --- packages/opencode/src/cli/cmd/debug/lsp.ts | 3 +- packages/opencode/src/lsp/client.ts | 574 +++++++++++++++--- packages/opencode/src/lsp/lsp.ts | 16 +- packages/opencode/src/lsp/server.ts | 2 +- packages/opencode/src/npm/index.ts | 15 +- packages/opencode/src/tool/apply_patch.ts | 2 +- packages/opencode/src/tool/edit.ts | 2 +- packages/opencode/src/tool/lsp.ts | 2 +- packages/opencode/src/tool/read.ts | 2 +- packages/opencode/src/tool/write.ts | 2 +- .../test/fixture/lsp/fake-lsp-server.js | 220 ++++++- packages/opencode/test/lsp/client.test.ts | 404 +++++++++++- 12 files changed, 1123 insertions(+), 121 deletions(-) diff --git a/packages/opencode/src/cli/cmd/debug/lsp.ts b/packages/opencode/src/cli/cmd/debug/lsp.ts index 185cab9c7..47db6358b 100644 --- a/packages/opencode/src/cli/cmd/debug/lsp.ts +++ b/packages/opencode/src/cli/cmd/debug/lsp.ts @@ -23,8 +23,7 @@ const DiagnosticsCommand = cmd({ const out = await AppRuntime.runPromise( LSP.Service.use((lsp) => Effect.gen(function* () { - yield* lsp.touchFile(args.file, true) - yield* Effect.sleep(1000) + yield* lsp.touchFile(args.file, "full") return yield* lsp.diagnostics() }), ), diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index b20e8ae7f..b0418ca3f 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -14,6 +14,16 @@ import { withTimeout } from "../util/timeout" import { Filesystem } from "../util" const DIAGNOSTICS_DEBOUNCE_MS = 150 +const DIAGNOSTICS_DOCUMENT_WAIT_TIMEOUT_MS = 5_000 +const DIAGNOSTICS_FULL_WAIT_TIMEOUT_MS = 10_000 +const DIAGNOSTICS_REQUEST_TIMEOUT_MS = 3_000 + +const INITIALIZE_TIMEOUT_MS = 45_000 + +// LSP spec constants +const FILE_CHANGE_CREATED = 1 +const FILE_CHANGE_CHANGED = 2 +const TEXT_DOCUMENT_SYNC_INCREMENTAL = 2 const log = Log.create({ service: "lsp.client" }) @@ -38,48 +48,194 @@ export const Event = { ), } +type DocumentDiagnosticReport = { + items?: Diagnostic[] + relatedDocuments?: Record +} + +type WorkspaceDiagnosticReport = { + items?: { + uri?: string + items?: Diagnostic[] + }[] +} + +type DiagnosticRequestResult = { + handled: boolean + matched: boolean + byFile: Map +} + +type CapabilityRegistration = { + id: string + method: string + registerOptions?: { + identifier?: string + workspaceDiagnostics?: boolean + } +} + +type ServerCapabilities = { + textDocumentSync?: + | number + | { + change?: number + } + diagnosticProvider?: unknown + [key: string]: unknown +} + +function getFilePath(uri: string) { + if (!uri.startsWith("file://")) return + return Filesystem.normalizePath(fileURLToPath(uri)) +} + +function getSyncKind(capabilities?: ServerCapabilities) { + if (!capabilities) return + const sync = capabilities.textDocumentSync + if (typeof sync === "number") return sync + return sync?.change +} + +function endPosition(text: string) { + const lines = text.split(/\r\n|\r|\n/) + return { + line: lines.length - 1, + character: lines.at(-1)?.length ?? 0, + } +} + +function dedupeDiagnostics(items: Diagnostic[]) { + const seen = new Set() + return items.filter((item) => { + const key = JSON.stringify({ + code: item.code, + severity: item.severity, + message: item.message, + source: item.source, + range: item.range, + }) + if (seen.has(key)) return false + seen.add(key) + return true + }) +} + +function configurationValue(settings: unknown, section?: string) { + if (!section) return settings ?? null + const result = section.split(".").reduce((acc, key) => { + if (!acc || typeof acc !== "object" || !(key in acc)) return undefined + return (acc as Record)[key] + }, settings) + return result ?? null +} + +// TypeScript's built-in LSP pushes diagnostics aggressively on first open. +// We seed the push cache on the very first publish so waitForFreshPush can +// resolve immediately instead of waiting for a second debounced push. +function shouldSeedDiagnosticsOnFirstPush(serverID: string) { + return serverID === "typescript" +} + export async function create(input: { serverID: string; server: LSPServer.Handle; root: string; directory: string }) { - const l = log.clone().tag("serverID", input.serverID) - l.info("starting client") + const logger = log.clone().tag("serverID", input.serverID) + logger.info("starting client") const connection = createMessageConnection( new StreamMessageReader(input.server.process.stdout as any), new StreamMessageWriter(input.server.process.stdin as any), ) + // Server stderr can contain both real errors and routine informational logs, + // which is normal stderr practice for some tools. Keep the raw stream at + // debug so users can opt in with --print-logs --log-level DEBUG without + // polluting normal logs. + input.server.process.stderr?.on("data", (data: Buffer) => { + const text = data.toString().trim() + if (text) logger.debug("server stderr", { text: text.slice(0, 1000) }) + }) + + // --- Connection state --- + + const pushDiagnostics = new Map() + const pullDiagnostics = new Map() + const published = new Map() + const diagnosticRegistrations = new Map() + const registrationListeners = new Set<() => void>() + const mergedDiagnostics = (filePath: string) => + dedupeDiagnostics([...(pushDiagnostics.get(filePath) ?? []), ...(pullDiagnostics.get(filePath) ?? [])]) + const updatePushDiagnostics = (filePath: string, next: Diagnostic[]) => { + pushDiagnostics.set(filePath, next) + Bus.publish(Event.Diagnostics, { path: filePath, serverID: input.serverID }) + } + const updatePullDiagnostics = (filePath: string, next: Diagnostic[]) => { + pullDiagnostics.set(filePath, next) + } + const emitRegistrationChange = () => { + for (const listener of [...registrationListeners]) listener() + } + + // --- LSP connection handlers --- - const diagnostics = new Map() connection.onNotification("textDocument/publishDiagnostics", (params) => { - const filePath = Filesystem.normalizePath(fileURLToPath(params.uri)) - l.info("textDocument/publishDiagnostics", { + const filePath = getFilePath(params.uri) + if (!filePath) return + logger.info("textDocument/publishDiagnostics", { path: filePath, count: params.diagnostics.length, + version: params.version, }) - const exists = diagnostics.has(filePath) - diagnostics.set(filePath, params.diagnostics) - if (!exists && input.serverID === "typescript") return - Bus.publish(Event.Diagnostics, { path: filePath, serverID: input.serverID }) + published.set(filePath, { + at: Date.now(), + version: typeof params.version === "number" ? params.version : undefined, + }) + if (shouldSeedDiagnosticsOnFirstPush(input.serverID) && !pushDiagnostics.has(filePath)) { + pushDiagnostics.set(filePath, params.diagnostics) + return + } + updatePushDiagnostics(filePath, params.diagnostics) }) connection.onRequest("window/workDoneProgress/create", (params) => { - l.info("window/workDoneProgress/create", params) + logger.info("window/workDoneProgress/create", params) return null }) - connection.onRequest("workspace/configuration", async () => { - // Return server initialization options - return [input.server.initialization ?? {}] + connection.onRequest("workspace/configuration", async (params) => { + const items = (params as { items?: { section?: string }[] }).items ?? [] + return items.map((item) => configurationValue(input.server.initialization, item.section)) + }) + connection.onRequest("client/registerCapability", async (params) => { + const registrations = (params as { registrations?: CapabilityRegistration[] }).registrations ?? [] + let changed = false + for (const registration of registrations) { + if (registration.method !== "textDocument/diagnostic") continue + diagnosticRegistrations.set(registration.id, registration) + changed = true + } + if (changed) emitRegistrationChange() + }) + connection.onRequest("client/unregisterCapability", async (params) => { + const registrations = (params as { unregisterations?: { id: string; method: string }[] }).unregisterations ?? [] + let changed = false + for (const registration of registrations) { + if (registration.method !== "textDocument/diagnostic") continue + diagnosticRegistrations.delete(registration.id) + changed = true + } + if (changed) emitRegistrationChange() }) - connection.onRequest("client/registerCapability", async () => {}) - connection.onRequest("client/unregisterCapability", async () => {}) connection.onRequest("workspace/workspaceFolders", async () => [ { name: "workspace", uri: pathToFileURL(input.root).href, }, ]) + connection.onRequest("workspace/diagnostic/refresh", async () => null) connection.listen() - l.info("sending initialize") - await withTimeout( - connection.sendRequest("initialize", { + // --- Initialize handshake --- + + logger.info("sending initialize") + const initialized = await withTimeout( + connection.sendRequest<{ capabilities?: ServerCapabilities }>("initialize", { rootUri: pathToFileURL(input.root).href, processId: input.server.process.pid, workspaceFolders: [ @@ -100,21 +256,28 @@ export async function create(input: { serverID: string; server: LSPServer.Handle didChangeWatchedFiles: { dynamicRegistration: true, }, + diagnostics: { + refreshSupport: false, + }, }, textDocument: { synchronization: { didOpen: true, didChange: true, }, + diagnostic: { + dynamicRegistration: true, + relatedDocumentSupport: true, + }, publishDiagnostics: { - versionSupport: true, + versionSupport: false, }, }, }, }), - 45_000, + INITIALIZE_TIMEOUT_MS, ).catch((err) => { - l.error("initialize error", { error: err }) + logger.error("initialize error", { error: err }) throw new InitializeError( { serverID: input.serverID }, { @@ -123,6 +286,9 @@ export async function create(input: { serverID: string; server: LSPServer.Handle ) }) + const syncKind = getSyncKind(initialized.capabilities) + const hasStaticPullDiagnostics = Boolean(initialized.capabilities?.diagnosticProvider) + await connection.sendNotification("initialized", {}) if (input.server.initialization) { @@ -131,9 +297,271 @@ export async function create(input: { serverID: string; server: LSPServer.Handle }) } - const files: { - [path: string]: number - } = {} + const files: Record = {} + + // --- Diagnostic helpers --- + + const mergeResults = (filePath: string, results: DiagnosticRequestResult[]) => { + const handled = results.some((result) => result.handled) + const matched = results.some((result) => result.matched) + if (!handled) return { handled: false, matched: false } + + const merged = new Map() + for (const result of results) { + for (const [target, items] of result.byFile.entries()) { + const existing = merged.get(target) ?? [] + merged.set(target, existing.concat(items)) + } + } + + if (matched && !merged.has(filePath)) merged.set(filePath, []) + for (const [target, items] of merged.entries()) { + updatePullDiagnostics(target, dedupeDiagnostics(items)) + } + + return { handled, matched } + } + + async function requestDiagnosticReport(filePath: string, identifier?: string): Promise { + const report = await withTimeout( + connection.sendRequest("textDocument/diagnostic", { + ...(identifier ? { identifier } : {}), + textDocument: { + uri: pathToFileURL(filePath).href, + }, + }), + DIAGNOSTICS_REQUEST_TIMEOUT_MS, + ).catch(() => null) + if (!report) return { handled: false, matched: false, byFile: new Map() } + + const byFile = new Map() + const push = (target: string, items: Diagnostic[]) => { + const existing = byFile.get(target) ?? [] + byFile.set(target, existing.concat(items)) + } + + let handled = false + let matched = false + if (Array.isArray(report.items)) { + push(filePath, report.items) + handled = true + matched = true + } + for (const [uri, related] of Object.entries(report.relatedDocuments ?? {})) { + const relatedPath = getFilePath(uri) + if (!relatedPath || !Array.isArray(related.items)) continue + push(relatedPath, related.items) + handled = true + matched = matched || relatedPath === filePath + } + + return { handled, matched, byFile } + } + + async function requestWorkspaceDiagnosticReport(filePath: string, identifier?: string): Promise { + const report = await withTimeout( + connection.sendRequest("workspace/diagnostic", { + ...(identifier ? { identifier } : {}), + previousResultIds: [], + }), + DIAGNOSTICS_REQUEST_TIMEOUT_MS, + ).catch(() => null) + if (!report) return { handled: false, matched: false, byFile: new Map() } + + const byFile = new Map() + let matched = false + for (const item of report.items ?? []) { + const relatedPath = item.uri ? getFilePath(item.uri) : undefined + if (!relatedPath || !Array.isArray(item.items)) continue + const existing = byFile.get(relatedPath) ?? [] + byFile.set(relatedPath, existing.concat(item.items)) + matched = matched || relatedPath === filePath + } + + return { handled: true, matched, byFile } + } + + function documentPullState() { + const documentRegistrations = [...diagnosticRegistrations.values()].filter( + (registration) => registration.registerOptions?.workspaceDiagnostics !== true, + ) + return { + documentIdentifiers: [...new Set(documentRegistrations.flatMap((registration) => registration.registerOptions?.identifier ?? []))], + supported: hasStaticPullDiagnostics || documentRegistrations.length > 0, + } + } + + function workspacePullState() { + const workspaceRegistrations = [...diagnosticRegistrations.values()].filter( + (registration) => registration.registerOptions?.workspaceDiagnostics === true, + ) + return { + workspaceIdentifiers: [...new Set(workspaceRegistrations.flatMap((registration) => registration.registerOptions?.identifier ?? []))], + supported: workspaceRegistrations.length > 0, + } + } + + const hasCurrentFileDiagnostics = (filePath: string, results: DiagnosticRequestResult[]) => + results.some((result) => (result.byFile.get(filePath)?.length ?? 0) > 0) + + async function requestDiagnostics( + filePath: string, + requests: Promise[], + done: (results: DiagnosticRequestResult[]) => boolean, + ) { + if (!requests.length) return { handled: false, matched: false } + + const results: DiagnosticRequestResult[] = [] + return new Promise<{ handled: boolean; matched: boolean }>((resolve) => { + let pending = requests.length + let resolved = false + const finish = (merged: { handled: boolean; matched: boolean }, force = false) => { + if (resolved) return + if (!force && !done(results)) return + resolved = true + resolve(merged) + } + + for (const request of requests) { + request.then((result) => { + results.push(result) + pending -= 1 + const merged = mergeResults(filePath, results) + finish(merged) + if (pending === 0) finish(merged, true) + }) + } + }) + } + + // LATENCY-CRITICAL: dispatch identifier pulls in parallel and unblock once one + // batch already produced diagnostics for the current file. Let slower pulls keep + // merging in the background; do not sequence identifier-by-identifier, and do + // not add a post-match settle/debounce delay. See PR #23771. + async function requestDocumentDiagnostics(filePath: string) { + const state = documentPullState() + if (!state.supported) return { handled: false, matched: false } + return requestDiagnostics( + filePath, + [ + requestDiagnosticReport(filePath), + ...state.documentIdentifiers.map((identifier) => requestDiagnosticReport(filePath, identifier)), + ], + (results) => hasCurrentFileDiagnostics(filePath, results), + ) + } + + async function requestFullDiagnostics(filePath: string) { + const documentState = documentPullState() + const workspaceState = workspacePullState() + if (!documentState.supported && !workspaceState.supported) return { handled: false, matched: false } + return mergeResults( + filePath, + await Promise.all([ + ...(documentState.supported ? [requestDiagnosticReport(filePath)] : []), + ...documentState.documentIdentifiers.map((identifier) => requestDiagnosticReport(filePath, identifier)), + ...(workspaceState.supported ? [requestWorkspaceDiagnosticReport(filePath)] : []), + ...workspaceState.workspaceIdentifiers.map((identifier) => requestWorkspaceDiagnosticReport(filePath, identifier)), + ]), + ) + } + + function waitForRegistrationChange(timeout: number) { + if (timeout <= 0) return Promise.resolve(false) + return new Promise((resolve) => { + let finished = false + let timer: ReturnType | undefined + const finish = (result: boolean) => { + if (finished) return + finished = true + if (timer) clearTimeout(timer) + registrationListeners.delete(listener) + resolve(result) + } + const listener = () => finish(true) + registrationListeners.add(listener) + timer = setTimeout(() => finish(false), timeout) + }) + } + + function waitForFreshPush(request: { path: string; version: number; after: number; timeout: number }) { + if (request.timeout <= 0) return Promise.resolve(false) + return new Promise((resolve) => { + let finished = false + let debounceTimer: ReturnType | undefined + let timeoutTimer: ReturnType | undefined + let unsub: (() => void) | undefined + const finish = (result: boolean) => { + if (finished) return + finished = true + if (debounceTimer) clearTimeout(debounceTimer) + if (timeoutTimer) clearTimeout(timeoutTimer) + unsub?.() + resolve(result) + } + const schedule = () => { + const hit = published.get(request.path) + if (!hit) return + if (typeof hit.version === "number" && hit.version !== request.version) return + if (hit.at < request.after && hit.version !== request.version) return + if (debounceTimer) clearTimeout(debounceTimer) + debounceTimer = setTimeout(() => finish(true), Math.max(0, DIAGNOSTICS_DEBOUNCE_MS - (Date.now() - hit.at))) + } + + timeoutTimer = setTimeout(() => finish(false), request.timeout) + unsub = Bus.subscribe(Event.Diagnostics, (event) => { + if (event.properties.path !== request.path || event.properties.serverID !== input.serverID) return + schedule() + }) + schedule() + }) + } + + async function waitForDocumentDiagnostics(request: { path: string; version: number; after?: number }) { + const startedAt = request.after ?? Date.now() + const pushWait = waitForFreshPush({ + path: request.path, + version: request.version, + after: startedAt, + timeout: DIAGNOSTICS_DOCUMENT_WAIT_TIMEOUT_MS, + }) + + while (Date.now() - startedAt < DIAGNOSTICS_DOCUMENT_WAIT_TIMEOUT_MS) { + const result = await requestDocumentDiagnostics(request.path) + if (result.matched) return + const remaining = DIAGNOSTICS_DOCUMENT_WAIT_TIMEOUT_MS - (Date.now() - startedAt) + if (remaining <= 0) return + const next = await Promise.race([ + pushWait.then((ready) => (ready ? "push" : "timeout" as const)), + waitForRegistrationChange(remaining).then((changed) => (changed ? "registration" : "timeout" as const)), + ]) + if (next !== "registration") return + } + } + + async function waitForFullDiagnostics(request: { path: string; version: number; after?: number }) { + const startedAt = request.after ?? Date.now() + const pushWait = waitForFreshPush({ + path: request.path, + version: request.version, + after: startedAt, + timeout: DIAGNOSTICS_FULL_WAIT_TIMEOUT_MS, + }) + + while (Date.now() - startedAt < DIAGNOSTICS_FULL_WAIT_TIMEOUT_MS) { + const result = await requestFullDiagnostics(request.path) + if (result.handled || result.matched) return + const remaining = DIAGNOSTICS_FULL_WAIT_TIMEOUT_MS - (Date.now() - startedAt) + if (remaining <= 0) return + const next = await Promise.race([ + pushWait.then((ready) => (ready ? "push" : "timeout" as const)), + waitForRegistrationChange(remaining).then((changed) => (changed ? "registration" : "timeout" as const)), + ]) + if (next !== "registration") return + } + } + + // --- Public API --- const result = { root: input.root, @@ -145,26 +573,32 @@ export async function create(input: { serverID: string; server: LSPServer.Handle }, notify: { async open(request: { path: string }) { - request.path = path.isAbsolute(request.path) ? request.path : path.resolve(input.directory, request.path) + request.path = Filesystem.normalizePath( + path.isAbsolute(request.path) ? request.path : path.resolve(input.directory, request.path), + ) const text = await Filesystem.readText(request.path) const extension = path.extname(request.path) const languageId = LANGUAGE_EXTENSIONS[extension] ?? "plaintext" - const version = files[request.path] - if (version !== undefined) { - log.info("workspace/didChangeWatchedFiles", request) + const document = files[request.path] + if (document !== undefined) { + // Do not wipe diagnostics on didChange. Some servers (e.g. clangd) only + // re-emit diagnostics when the content actually changes, so clearing + // here would lose errors for no-op touchFile calls. Let the server's + // next push/pull overwrite naturally. + logger.info("workspace/didChangeWatchedFiles", request) await connection.sendNotification("workspace/didChangeWatchedFiles", { changes: [ { uri: pathToFileURL(request.path).href, - type: 2, // Changed + type: FILE_CHANGE_CHANGED, }, ], }) - const next = version + 1 - files[request.path] = next - log.info("textDocument/didChange", { + const next = document.version + 1 + files[request.path] = { version: next, text } + logger.info("textDocument/didChange", { path: request.path, version: next, }) @@ -173,23 +607,35 @@ export async function create(input: { serverID: string; server: LSPServer.Handle uri: pathToFileURL(request.path).href, version: next, }, - contentChanges: [{ text }], + contentChanges: + syncKind === TEXT_DOCUMENT_SYNC_INCREMENTAL + ? [ + { + range: { + start: { line: 0, character: 0 }, + end: endPosition(document.text), + }, + text, + }, + ] + : [{ text }], }) - return + return next } - log.info("workspace/didChangeWatchedFiles", request) + logger.info("workspace/didChangeWatchedFiles", request) await connection.sendNotification("workspace/didChangeWatchedFiles", { changes: [ { uri: pathToFileURL(request.path).href, - type: 1, // Created + type: FILE_CHANGE_CREATED, }, ], }) - log.info("textDocument/didOpen", request) - diagnostics.delete(request.path) + logger.info("textDocument/didOpen", request) + pushDiagnostics.delete(request.path) + pullDiagnostics.delete(request.path) await connection.sendNotification("textDocument/didOpen", { textDocument: { uri: pathToFileURL(request.path).href, @@ -198,52 +644,42 @@ export async function create(input: { serverID: string; server: LSPServer.Handle text, }, }) - files[request.path] = 0 - return + files[request.path] = { version: 0, text } + return 0 }, }, get diagnostics() { - return diagnostics + const result = new Map() + for (const key of new Set([...pushDiagnostics.keys(), ...pullDiagnostics.keys()])) { + result.set(key, mergedDiagnostics(key)) + } + return result }, - async waitForDiagnostics(request: { path: string }) { + async waitForDiagnostics(request: { path: string; version: number; mode?: "document" | "full"; after?: number }) { const normalizedPath = Filesystem.normalizePath( path.isAbsolute(request.path) ? request.path : path.resolve(input.directory, request.path), ) - log.info("waiting for diagnostics", { path: normalizedPath }) - let unsub: () => void - let debounceTimer: ReturnType | undefined - return await withTimeout( - new Promise((resolve) => { - unsub = Bus.subscribe(Event.Diagnostics, (event) => { - if (event.properties.path === normalizedPath && event.properties.serverID === result.serverID) { - // Debounce to allow LSP to send follow-up diagnostics (e.g., semantic after syntax) - if (debounceTimer) clearTimeout(debounceTimer) - debounceTimer = setTimeout(() => { - log.info("got diagnostics", { path: normalizedPath }) - unsub?.() - resolve() - }, DIAGNOSTICS_DEBOUNCE_MS) - } - }) - }), - 3000, - ) - .catch(() => {}) - .finally(() => { - if (debounceTimer) clearTimeout(debounceTimer) - unsub?.() - }) + logger.info("waiting for diagnostics", { + path: normalizedPath, + mode: request.mode ?? "full", + version: request.version, + }) + if (request.mode === "document") { + await waitForDocumentDiagnostics({ path: normalizedPath, version: request.version, after: request.after }) + return + } + await waitForFullDiagnostics({ path: normalizedPath, version: request.version, after: request.after }) }, async shutdown() { - l.info("shutting down") + logger.info("shutting down") connection.end() connection.dispose() await Process.stop(input.server.process) - l.info("shutdown") + logger.info("shutdown") }, } - l.info("initialized") + logger.info("initialized") return result } diff --git a/packages/opencode/src/lsp/lsp.ts b/packages/opencode/src/lsp/lsp.ts index 833285e7b..4c46cd9aa 100644 --- a/packages/opencode/src/lsp/lsp.ts +++ b/packages/opencode/src/lsp/lsp.ts @@ -136,7 +136,7 @@ export interface Interface { readonly init: () => Effect.Effect readonly status: () => Effect.Effect readonly hasClients: (file: string) => Effect.Effect - readonly touchFile: (input: string, waitForDiagnostics?: boolean) => Effect.Effect + readonly touchFile: (input: string, diagnostics?: "document" | "full") => Effect.Effect readonly diagnostics: () => Effect.Effect> readonly hover: (input: LocInput) => Effect.Effect readonly definition: (input: LocInput) => Effect.Effect @@ -358,15 +358,21 @@ export const layer = Layer.effect( }) }) - const touchFile = Effect.fn("LSP.touchFile")(function* (input: string, waitForDiagnostics?: boolean) { + const touchFile = Effect.fn("LSP.touchFile")(function* (input: string, diagnostics?: "document" | "full") { log.info("touching file", { file: input }) const clients = yield* getClients(input) yield* Effect.promise(() => Promise.all( clients.map(async (client) => { - const wait = waitForDiagnostics ? client.waitForDiagnostics({ path: input }) : Promise.resolve() - await client.notify.open({ path: input }) - return wait + const after = Date.now() + const version = await client.notify.open({ path: input }) + if (!diagnostics) return + return client.waitForDiagnostics({ + path: input, + version, + mode: diagnostics, + after, + }) }), ).catch((err) => { log.error("failed to touch file", { err, file: input }) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 8bb70a511..a0cb8fe38 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -490,7 +490,7 @@ export const Pyright: Info = { const args = [] if (!binary) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - const resolved = await Npm.which("pyright") + const resolved = await Npm.which("pyright", "pyright-langserver") if (!resolved) return binary = resolved } diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 477e99e06..fc8497d20 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -34,7 +34,7 @@ export interface Interface { }, ) => Effect.Effect readonly outdated: (pkg: string, cachedVersion: string) => Effect.Effect - readonly which: (pkg: string) => Effect.Effect> + readonly which: (pkg: string, bin?: string) => Effect.Effect> } export class Service extends Context.Service()("@opencode/Npm") {} @@ -207,7 +207,7 @@ export const layer = Layer.effect( return }, Effect.scoped) - const which = Effect.fn("Npm.which")(function* (pkg: string) { + const which = Effect.fn("Npm.which")(function* (pkg: string, bin?: string) { const dir = directory(pkg) const binDir = path.join(dir, "node_modules", ".bin") @@ -215,6 +215,9 @@ export const layer = Layer.effect( const files = yield* fs.readDirectory(binDir).pipe(Effect.catch(() => Effect.succeed([] as string[]))) if (files.length === 0) return Option.none() + // Caller picked a specific bin (e.g. pyright exposes both `pyright` and + // `pyright-langserver`); trust the hint if the package provides it. + if (bin) return files.includes(bin) ? Option.some(bin) : Option.none() if (files.length === 1) return Option.some(files[0]) const pkgJson = yield* afs.readJson(path.join(dir, "node_modules", pkg, "package.json")).pipe(Effect.option) @@ -223,11 +226,11 @@ export const layer = Layer.effect( const parsed = pkgJson.value as { bin?: string | Record } if (parsed?.bin) { const unscoped = pkg.startsWith("@") ? pkg.split("/")[1] : pkg - const bin = parsed.bin - if (typeof bin === "string") return Option.some(unscoped) - const keys = Object.keys(bin) + const parsedBin = parsed.bin + if (typeof parsedBin === "string") return Option.some(unscoped) + const keys = Object.keys(parsedBin) if (keys.length === 1) return Option.some(keys[0]) - return bin[unscoped] ? Option.some(unscoped) : Option.some(keys[0]) + return parsedBin[unscoped] ? Option.some(unscoped) : Option.some(keys[0]) } } diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts index a4cf1e853..33112c43c 100644 --- a/packages/opencode/src/tool/apply_patch.ts +++ b/packages/opencode/src/tool/apply_patch.ts @@ -258,7 +258,7 @@ export const ApplyPatchTool = Tool.define( for (const change of fileChanges) { if (change.type === "delete") continue const target = change.movePath ?? change.filePath - yield* lsp.touchFile(target, true) + yield* lsp.touchFile(target, "document") } const diagnostics = yield* lsp.diagnostics() diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 858d14e04..35dd85b47 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -186,7 +186,7 @@ export const EditTool = Tool.define( }) let output = "Edit applied successfully." - yield* lsp.touchFile(filePath, true) + yield* lsp.touchFile(filePath, "document") const diagnostics = yield* lsp.diagnostics() const normalizedFilePath = AppFileSystem.normalizePath(filePath) const block = LSP.Diagnostic.report(filePath, diagnostics[normalizedFilePath] ?? []) diff --git a/packages/opencode/src/tool/lsp.ts b/packages/opencode/src/tool/lsp.ts index 263bfe81d..0a0edc61e 100644 --- a/packages/opencode/src/tool/lsp.ts +++ b/packages/opencode/src/tool/lsp.ts @@ -55,7 +55,7 @@ export const LspTool = Tool.define( const available = yield* lsp.hasClients(file) if (!available) throw new Error("No LSP server available for this file type.") - yield* lsp.touchFile(file, true) + yield* lsp.touchFile(file, "document") const result: unknown[] = yield* (() => { switch (args.operation) { diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index c9b304862..a9b95346a 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -75,7 +75,7 @@ export const ReadTool = Tool.define( }) const warm = Effect.fn("ReadTool.warm")(function* (filepath: string) { - yield* lsp.touchFile(filepath, false).pipe(Effect.ignore, Effect.forkIn(scope)) + yield* lsp.touchFile(filepath).pipe(Effect.ignore, Effect.forkIn(scope)) }) const readSample = Effect.fn("ReadTool.readSample")(function* ( diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 79ed58519..80198f455 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -67,7 +67,7 @@ export const WriteTool = Tool.define( }) let output = "Wrote file successfully." - yield* lsp.touchFile(filepath, true) + yield* lsp.touchFile(filepath, "document") const diagnostics = yield* lsp.diagnostics() const normalizedFilepath = AppFileSystem.normalizePath(filepath) let projectDiagnosticsCount = 0 diff --git a/packages/opencode/test/fixture/lsp/fake-lsp-server.js b/packages/opencode/test/fixture/lsp/fake-lsp-server.js index be62f96f3..e6818009e 100644 --- a/packages/opencode/test/fixture/lsp/fake-lsp-server.js +++ b/packages/opencode/test/fixture/lsp/fake-lsp-server.js @@ -1,7 +1,23 @@ // Simple JSON-RPC 2.0 LSP-like fake server over stdio -// Implements a minimal LSP handshake and triggers a request upon notification let nextId = 1 +let readBuffer = Buffer.alloc(0) +let lastChange = null +let initializeParams = null +let diagnosticRequestCount = 0 +let registeredCapability = false +const pendingClientRequests = new Map() +let pullConfig = { + delayMs: 0, + registerOn: undefined, + registrations: [], + documentDiagnostics: [], + documentDiagnosticsByIdentifier: {}, + documentDelayMsByIdentifier: {}, + workspaceDiagnostics: [], + workspaceDiagnosticsByIdentifier: {}, + workspaceDelayMsByIdentifier: {}, +} function encode(message) { const json = JSON.stringify(message) @@ -14,29 +30,19 @@ function decodeFrames(buffer) { let idx while ((idx = buffer.indexOf("\r\n\r\n")) !== -1) { const header = buffer.slice(0, idx).toString("utf8") - const m = /Content-Length:\s*(\d+)/i.exec(header) - const len = m ? parseInt(m[1], 10) : 0 + const match = /Content-Length:\s*(\d+)/i.exec(header) + const length = match ? parseInt(match[1], 10) : 0 const bodyStart = idx + 4 - const bodyEnd = bodyStart + len + const bodyEnd = bodyStart + length if (buffer.length < bodyEnd) break - const body = buffer.slice(bodyStart, bodyEnd).toString("utf8") - results.push(body) + results.push(buffer.slice(bodyStart, bodyEnd).toString("utf8")) buffer = buffer.slice(bodyEnd) } return { messages: results, rest: buffer } } -let readBuffer = Buffer.alloc(0) - -process.stdin.on("data", (chunk) => { - readBuffer = Buffer.concat([readBuffer, chunk]) - const { messages, rest } = decodeFrames(readBuffer) - readBuffer = rest - for (const m of messages) handle(m) -}) - -function send(msg) { - process.stdout.write(encode(msg)) +function send(message) { + process.stdout.write(encode(message)) } function sendRequest(method, params) { @@ -45,6 +51,50 @@ function sendRequest(method, params) { return id } +function sendResponse(id, result) { + send({ jsonrpc: "2.0", id, result }) +} + +function sendNotification(method, params) { + send({ jsonrpc: "2.0", method, params }) +} + +function maybeRegister(method) { + if (pullConfig.registerOn !== method || registeredCapability) return + registeredCapability = true + sendRequest("client/registerCapability", { + registrations: pullConfig.registrations.map((registration, index) => ({ + id: registration.id ?? `pull-${index}`, + method: registration.method ?? "textDocument/diagnostic", + registerOptions: registration.registerOptions ?? registration, + })), + }) +} + +function delayed(id, result, delayMs = pullConfig.delayMs) { + if (!delayMs) { + sendResponse(id, result) + return + } + setTimeout(() => sendResponse(id, result), delayMs) +} + +function diagnosticsForIdentifier(identifier) { + return pullConfig.documentDiagnosticsByIdentifier[identifier] ?? pullConfig.documentDiagnostics +} + +function workspaceDiagnosticsForIdentifier(identifier) { + return pullConfig.workspaceDiagnosticsByIdentifier[identifier] ?? pullConfig.workspaceDiagnostics +} + +function documentDelayForIdentifier(identifier) { + return pullConfig.documentDelayMsByIdentifier[identifier] ?? pullConfig.delayMs +} + +function workspaceDelayForIdentifier(identifier) { + return pullConfig.workspaceDelayMsByIdentifier[identifier] ?? pullConfig.delayMs +} + function handle(raw) { let data try { @@ -52,24 +102,148 @@ function handle(raw) { } catch { return } + + if (typeof data.method === "undefined" && typeof data.id !== "undefined") { + const pending = pendingClientRequests.get(data.id) + if (!pending) return + pendingClientRequests.delete(data.id) + sendResponse(pending, data.result ?? null) + return + } + if (data.method === "initialize") { - send({ jsonrpc: "2.0", id: data.id, result: { capabilities: {} } }) + initializeParams = data.params + sendResponse(data.id, { + capabilities: { + textDocumentSync: { + change: 2, + }, + }, + }) return } - if (data.method === "initialized") { + + if (data.method === "test/get-initialize-params") { + sendResponse(data.id, initializeParams) return } - if (data.method === "workspace/didChangeConfiguration") { + + if (data.method === "test/request-configuration") { + const id = sendRequest("workspace/configuration", data.params) + pendingClientRequests.set(id, data.id) return } + + if (data.method === "initialized" || data.method === "workspace/didChangeConfiguration") { + return + } + + if (data.method === "textDocument/didOpen") { + maybeRegister("didOpen") + return + } + + if (data.method === "textDocument/didChange") { + lastChange = data.params + maybeRegister("didChange") + return + } + if (data.method === "test/trigger") { const method = data.params && data.params.method + if (method === "client/registerCapability") { + sendRequest(method, { + registrations: [ + { + id: "test-diagnostic-registration", + method: "textDocument/diagnostic", + registerOptions: { identifier: "syntax" }, + }, + ], + }) + return + } + if (method === "client/unregisterCapability") { + sendRequest(method, { + unregisterations: [{ id: "test-diagnostic-registration", method: "textDocument/diagnostic" }], + }) + return + } if (method) sendRequest(method, {}) return } - if (typeof data.id !== "undefined") { - // Respond OK to any request from client to keep transport flowing - send({ jsonrpc: "2.0", id: data.id, result: null }) + + if (data.method === "test/configure-pull-diagnostics") { + pullConfig = { + delayMs: data.params?.delayMs ?? 0, + registerOn: data.params?.registerOn, + registrations: data.params?.registrations ?? [], + documentDiagnostics: data.params?.documentDiagnostics ?? [], + documentDiagnosticsByIdentifier: data.params?.documentDiagnosticsByIdentifier ?? {}, + documentDelayMsByIdentifier: data.params?.documentDelayMsByIdentifier ?? {}, + workspaceDiagnostics: data.params?.workspaceDiagnostics ?? [], + workspaceDiagnosticsByIdentifier: data.params?.workspaceDiagnosticsByIdentifier ?? {}, + workspaceDelayMsByIdentifier: data.params?.workspaceDelayMsByIdentifier ?? {}, + } + registeredCapability = false + sendResponse(data.id, null) return } + + if (data.method === "test/register-configured-pull-diagnostics") { + maybeRegister(undefined) + sendResponse(data.id, null) + return + } + + if (data.method === "test/publish-diagnostics") { + sendNotification("textDocument/publishDiagnostics", data.params) + return + } + + if (data.method === "test/get-last-change") { + sendResponse(data.id, lastChange) + return + } + + if (data.method === "test/get-diagnostic-request-count") { + sendResponse(data.id, diagnosticRequestCount) + return + } + + if (data.method === "textDocument/diagnostic") { + diagnosticRequestCount += 1 + delayed( + data.id, + { + kind: "full", + items: diagnosticsForIdentifier(data.params?.identifier ?? ""), + }, + documentDelayForIdentifier(data.params?.identifier ?? ""), + ) + return + } + + if (data.method === "workspace/diagnostic") { + diagnosticRequestCount += 1 + delayed( + data.id, + { + items: workspaceDiagnosticsForIdentifier(data.params?.identifier ?? ""), + }, + workspaceDelayForIdentifier(data.params?.identifier ?? ""), + ) + return + } + + if (typeof data.id !== "undefined") { + sendResponse(data.id, null) + } } + +process.stdin.on("data", (chunk) => { + readBuffer = Buffer.concat([readBuffer, chunk]) + const { messages, rest } = decodeFrames(readBuffer) + readBuffer = rest + for (const message of messages) handle(message) +}) diff --git a/packages/opencode/test/lsp/client.test.ts b/packages/opencode/test/lsp/client.test.ts index d6eaa317f..4862f6839 100644 --- a/packages/opencode/test/lsp/client.test.ts +++ b/packages/opencode/test/lsp/client.test.ts @@ -1,11 +1,12 @@ -import { describe, expect, test, beforeEach } from "bun:test" +import { beforeEach, describe, expect, test } from "bun:test" import path from "path" +import { pathToFileURL } from "url" +import { tmpdir } from "../fixture/fixture" import { LSPClient } from "../../src/lsp" import { LSPServer } from "../../src/lsp" import { Instance } from "../../src/project/instance" import { Log } from "../../src/util" -// Minimal fake LSP server that speaks JSON-RPC over stdio function spawnFakeServer() { const { spawn } = require("child_process") const serverPath = path.join(__dirname, "../fixture/lsp/fake-lsp-server.js") @@ -39,10 +40,8 @@ describe("LSPClient interop", () => { method: "workspace/workspaceFolders", }) - await new Promise((r) => setTimeout(r, 100)) - + await new Promise((resolve) => setTimeout(resolve, 100)) expect(client.connection).toBeDefined() - await client.shutdown() }) @@ -64,10 +63,8 @@ describe("LSPClient interop", () => { method: "client/registerCapability", }) - await new Promise((r) => setTimeout(r, 100)) - + await new Promise((resolve) => setTimeout(resolve, 100)) expect(client.connection).toBeDefined() - await client.shutdown() }) @@ -89,10 +86,397 @@ describe("LSPClient interop", () => { method: "client/unregisterCapability", }) - await new Promise((r) => setTimeout(r, 100)) - + await new Promise((resolve) => setTimeout(resolve, 100)) expect(client.connection).toBeDefined() + await client.shutdown() + }) + + test("initialize does not overclaim unsupported diagnostics capabilities", async () => { + const handle = spawnFakeServer() as any + + const client = await Instance.provide({ + directory: process.cwd(), + fn: () => + LSPClient.create({ + serverID: "fake", + server: handle as unknown as LSPServer.Handle, + root: process.cwd(), + directory: process.cwd(), + }), + }) + + const params = await client.connection.sendRequest("test/get-initialize-params", {}) + expect(params.capabilities.workspace.diagnostics.refreshSupport).toBe(false) + expect(params.capabilities.textDocument.publishDiagnostics.versionSupport).toBe(false) await client.shutdown() }) + + test("workspace/configuration returns one result per requested item", async () => { + const handle = spawnFakeServer() as any + const initialization = { + alpha: { + beta: 1, + }, + gamma: true, + } + + const client = await Instance.provide({ + directory: process.cwd(), + fn: () => + LSPClient.create({ + serverID: "fake", + server: { + ...(handle as unknown as LSPServer.Handle), + initialization, + }, + root: process.cwd(), + directory: process.cwd(), + }), + }) + + const response = await client.connection.sendRequest("test/request-configuration", { + items: [{ section: "alpha" }, { section: "alpha.beta" }, { section: "missing" }, {}], + }) + + expect(response).toEqual([{ beta: 1 }, 1, null, initialization]) + + await client.shutdown() + }) + + test("sends ranged didChange for incremental sync servers", async () => { + const handle = spawnFakeServer() as any + await using tmp = await tmpdir() + const file = path.join(tmp.path, "client.ts") + await Bun.write(file, "first\n") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const client = await LSPClient.create({ + serverID: "fake", + server: handle as unknown as LSPServer.Handle, + root: tmp.path, + directory: tmp.path, + }) + + await client.notify.open({ path: file }) + await Bun.write(file, "second\nthird\n") + await client.notify.open({ path: file }) + + const change = await client.connection.sendRequest<{ + textDocument: { version: number } + contentChanges: { + range?: { start: { line: number; character: number }; end: { line: number; character: number } } + text: string + }[] + }>("test/get-last-change", {}) + expect(change.textDocument.version).toBe(1) + expect(change.contentChanges).toEqual([ + { + range: { + start: { line: 0, character: 0 }, + end: { line: 1, character: 0 }, + }, + text: "second\nthird\n", + }, + ]) + + await client.shutdown() + }, + }) + }) + + test("document mode falls back to push diagnostics", async () => { + const handle = spawnFakeServer() as any + await using tmp = await tmpdir() + const file = path.join(tmp.path, "client.ts") + await Bun.write(file, "const x = 1\n") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const client = await LSPClient.create({ + serverID: "fake", + server: handle as unknown as LSPServer.Handle, + root: tmp.path, + directory: tmp.path, + }) + + const version = await client.notify.open({ path: file }) + const wait = client.waitForDiagnostics({ path: file, version, mode: "document" }) + await client.connection.sendNotification("test/publish-diagnostics", { + uri: pathToFileURL(file).href, + version, + diagnostics: [ + { + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 5 }, + }, + message: "push diagnostic", + severity: 1, + }, + ], + }) + await wait + + const diagnostics = client.diagnostics.get(file) ?? [] + expect(diagnostics).toHaveLength(1) + expect(diagnostics[0]?.message).toBe("push diagnostic") + + const count = await client.connection.sendRequest("test/get-diagnostic-request-count", {}) + expect(count).toBe(0) + + await client.shutdown() + }, + }) + }) + + test("document mode accepts matching push diagnostics published before waiting", async () => { + const handle = spawnFakeServer() as any + await using tmp = await tmpdir() + const file = path.join(tmp.path, "client.ts") + await Bun.write(file, "const x = 1\n") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const client = await LSPClient.create({ + serverID: "fake", + server: handle as unknown as LSPServer.Handle, + root: tmp.path, + directory: tmp.path, + }) + + const version = await client.notify.open({ path: file }) + await client.connection.sendNotification("test/publish-diagnostics", { + uri: pathToFileURL(file).href, + version, + diagnostics: [ + { + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 5 }, + }, + message: "push diagnostic", + severity: 1, + }, + ], + }) + + for (let i = 0; i < 20 && (client.diagnostics.get(file)?.length ?? 0) === 0; i++) { + await new Promise((resolve) => setTimeout(resolve, 25)) + } + + expect(client.diagnostics.get(file)?.[0]?.message).toBe("push diagnostic") + + const started = Date.now() + await client.waitForDiagnostics({ path: file, version, mode: "document" }) + expect(Date.now() - started).toBeLessThan(1_000) + + await client.shutdown() + }, + }) + }) + + test("document mode waits for pull diagnostics", async () => { + const handle = spawnFakeServer() as any + await using tmp = await tmpdir() + const file = path.join(tmp.path, "client.cs") + await Bun.write(file, "class C {}\n") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const client = await LSPClient.create({ + serverID: "fake", + server: handle as unknown as LSPServer.Handle, + root: tmp.path, + directory: tmp.path, + }) + + await client.connection.sendRequest("test/configure-pull-diagnostics", { + registerOn: "didOpen", + registrations: [{ identifier: "DocumentCompilerSemantic" }], + documentDiagnosticsByIdentifier: { + DocumentCompilerSemantic: [ + { + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 5 }, + }, + message: "pull diagnostic", + severity: 1, + }, + ], + }, + }) + + const version = await client.notify.open({ path: file }) + await client.waitForDiagnostics({ path: file, version, mode: "document" }) + + const diagnostics = client.diagnostics.get(file) ?? [] + expect(diagnostics).toHaveLength(1) + expect(diagnostics[0]?.message).toBe("pull diagnostic") + + const count = await client.connection.sendRequest("test/get-diagnostic-request-count", {}) + expect(count).toBeGreaterThan(0) + + await client.shutdown() + }, + }) + }) + + test("document mode does not wait for the slowest pull identifier after current-file diagnostics arrive", async () => { + const handle = spawnFakeServer() as any + await using tmp = await tmpdir() + const file = path.join(tmp.path, "client.cs") + await Bun.write(file, "class C {}\n") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const client = await LSPClient.create({ + serverID: "fake", + server: handle as unknown as LSPServer.Handle, + root: tmp.path, + directory: tmp.path, + }) + + await client.connection.sendRequest("test/configure-pull-diagnostics", { + registrations: [{ identifier: "fast" }, { identifier: "slow" }], + documentDiagnosticsByIdentifier: { + fast: [ + { + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 5 }, + }, + message: "fast diagnostic", + severity: 1, + }, + ], + slow: [], + }, + documentDelayMsByIdentifier: { + slow: 2_500, + }, + }) + + const version = await client.notify.open({ path: file }) + await client.connection.sendRequest("test/register-configured-pull-diagnostics", {}) + await new Promise((resolve) => setTimeout(resolve, 100)) + const started = Date.now() + await client.waitForDiagnostics({ path: file, version, mode: "document" }) + + expect(Date.now() - started).toBeLessThan(1_000) + expect(client.diagnostics.get(file)?.[0]?.message).toBe("fast diagnostic") + expect(await client.connection.sendRequest("test/get-diagnostic-request-count", {})).toBeGreaterThan(1) + + await client.shutdown() + }, + }) + }) + + test("full mode includes workspace pull diagnostics", async () => { + const handle = spawnFakeServer() as any + await using tmp = await tmpdir() + const file = path.join(tmp.path, "client.cs") + const related = path.join(tmp.path, "other.cs") + await Bun.write(file, "class C {}\n") + await Bun.write(related, "class D {}\n") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const client = await LSPClient.create({ + serverID: "fake", + server: handle as unknown as LSPServer.Handle, + root: tmp.path, + directory: tmp.path, + }) + + await client.connection.sendRequest("test/configure-pull-diagnostics", { + registerOn: "didOpen", + registrations: [ + { identifier: "DocumentCompilerSemantic" }, + { identifier: "WorkspaceDocumentsAndProject", workspaceDiagnostics: true }, + ], + documentDiagnosticsByIdentifier: { + DocumentCompilerSemantic: [ + { + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 5 }, + }, + message: "current file", + severity: 1, + }, + ], + }, + workspaceDiagnosticsByIdentifier: { + WorkspaceDocumentsAndProject: [ + { + uri: pathToFileURL(related).href, + items: [ + { + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 5 }, + }, + message: "workspace file", + severity: 1, + }, + ], + }, + ], + }, + }) + + const version = await client.notify.open({ path: file }) + await client.waitForDiagnostics({ path: file, version, mode: "full" }) + + expect(client.diagnostics.get(file)?.[0]?.message).toBe("current file") + expect(client.diagnostics.get(related)?.[0]?.message).toBe("workspace file") + + await client.shutdown() + }, + }) + }) + + test("full mode treats an empty workspace pull response as handled", async () => { + const handle = spawnFakeServer() as any + await using tmp = await tmpdir() + const file = path.join(tmp.path, "client.cs") + await Bun.write(file, "class C {}\n") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const client = await LSPClient.create({ + serverID: "fake", + server: handle as unknown as LSPServer.Handle, + root: tmp.path, + directory: tmp.path, + }) + + await client.connection.sendRequest("test/configure-pull-diagnostics", { + registerOn: "didOpen", + registrations: [{ identifier: "WorkspaceDocumentsAndProject", workspaceDiagnostics: true }], + workspaceDiagnosticsByIdentifier: { + WorkspaceDocumentsAndProject: [], + }, + }) + + const version = await client.notify.open({ path: file }) + const started = Date.now() + await client.waitForDiagnostics({ path: file, version, mode: "full" }) + + expect(Date.now() - started).toBeLessThan(1_000) + + await client.shutdown() + }, + }) + }) }) From 1cd4c92242ea75460100a0d69855a90938e07832 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 22 Apr 2026 23:25:07 +0000 Subject: [PATCH 066/426] chore: generate --- packages/opencode/src/lsp/client.ts | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index b0418ca3f..f6d5110a6 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -358,7 +358,10 @@ export async function create(input: { serverID: string; server: LSPServer.Handle return { handled, matched, byFile } } - async function requestWorkspaceDiagnosticReport(filePath: string, identifier?: string): Promise { + async function requestWorkspaceDiagnosticReport( + filePath: string, + identifier?: string, + ): Promise { const report = await withTimeout( connection.sendRequest("workspace/diagnostic", { ...(identifier ? { identifier } : {}), @@ -386,7 +389,9 @@ export async function create(input: { serverID: string; server: LSPServer.Handle (registration) => registration.registerOptions?.workspaceDiagnostics !== true, ) return { - documentIdentifiers: [...new Set(documentRegistrations.flatMap((registration) => registration.registerOptions?.identifier ?? []))], + documentIdentifiers: [ + ...new Set(documentRegistrations.flatMap((registration) => registration.registerOptions?.identifier ?? [])), + ], supported: hasStaticPullDiagnostics || documentRegistrations.length > 0, } } @@ -396,7 +401,9 @@ export async function create(input: { serverID: string; server: LSPServer.Handle (registration) => registration.registerOptions?.workspaceDiagnostics === true, ) return { - workspaceIdentifiers: [...new Set(workspaceRegistrations.flatMap((registration) => registration.registerOptions?.identifier ?? []))], + workspaceIdentifiers: [ + ...new Set(workspaceRegistrations.flatMap((registration) => registration.registerOptions?.identifier ?? [])), + ], supported: workspaceRegistrations.length > 0, } } @@ -461,7 +468,9 @@ export async function create(input: { serverID: string; server: LSPServer.Handle ...(documentState.supported ? [requestDiagnosticReport(filePath)] : []), ...documentState.documentIdentifiers.map((identifier) => requestDiagnosticReport(filePath, identifier)), ...(workspaceState.supported ? [requestWorkspaceDiagnosticReport(filePath)] : []), - ...workspaceState.workspaceIdentifiers.map((identifier) => requestWorkspaceDiagnosticReport(filePath, identifier)), + ...workspaceState.workspaceIdentifiers.map((identifier) => + requestWorkspaceDiagnosticReport(filePath, identifier), + ), ]), ) } @@ -532,8 +541,8 @@ export async function create(input: { serverID: string; server: LSPServer.Handle const remaining = DIAGNOSTICS_DOCUMENT_WAIT_TIMEOUT_MS - (Date.now() - startedAt) if (remaining <= 0) return const next = await Promise.race([ - pushWait.then((ready) => (ready ? "push" : "timeout" as const)), - waitForRegistrationChange(remaining).then((changed) => (changed ? "registration" : "timeout" as const)), + pushWait.then((ready) => (ready ? "push" : ("timeout" as const))), + waitForRegistrationChange(remaining).then((changed) => (changed ? "registration" : ("timeout" as const))), ]) if (next !== "registration") return } @@ -554,8 +563,8 @@ export async function create(input: { serverID: string; server: LSPServer.Handle const remaining = DIAGNOSTICS_FULL_WAIT_TIMEOUT_MS - (Date.now() - startedAt) if (remaining <= 0) return const next = await Promise.race([ - pushWait.then((ready) => (ready ? "push" : "timeout" as const)), - waitForRegistrationChange(remaining).then((changed) => (changed ? "registration" : "timeout" as const)), + pushWait.then((ready) => (ready ? "push" : ("timeout" as const))), + waitForRegistrationChange(remaining).then((changed) => (changed ? "registration" : ("timeout" as const))), ]) if (next !== "registration") return } From 6387b35a2d5b36fc1ac4dd3270fa2da66cca8111 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:08:14 +1000 Subject: [PATCH 067/426] log session sdk errors (#23652) --- packages/app/src/context/global-sync.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 313ff2965..7c819918c 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -295,6 +295,19 @@ function createGlobalSync() { const event = e.details const recent = bootingRoot || Date.now() - bootedAt < 1500 + if (event.type === "session.error") { + const error = event.properties.error + if (error?.name !== "MessageAbortedError") { + console.error("[global-sync] session error", { + scope: directory === "global" ? "global" : "workspace", + directory: directory === "global" ? undefined : directory, + project: directory === "global" ? undefined : getFilename(directory), + sessionID: event.properties.sessionID, + error, + }) + } + } + if (directory === "global") { applyGlobalEvent({ event, From ac26394fcb280592a8ecddf903a3a7116c841f39 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:19:36 +1000 Subject: [PATCH 068/426] fix(beta): PR resolvers/smoke check should typecheck all pacakges (#23913) --- script/beta.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/script/beta.ts b/script/beta.ts index 34d9dab1f..7c558d1e7 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -61,7 +61,7 @@ async function typecheck() { console.log(" Running typecheck...") try { - await $`bun typecheck`.cwd("packages/opencode") + await $`bun typecheck` return true } catch (err) { console.log(`Typecheck failed: ${err}`) @@ -113,7 +113,7 @@ async function fix(pr: PR, files: string[], prs: PR[], applied: number[], idx: n "If bun.lock is conflicted, do not hand-merge it. Delete bun.lock and run bun install after the code conflicts are resolved.", "If a PR already deleted a file/directory, do not re-add it, instead apply changes in the new semantic location.", "If a PR already changed an import, keep that change.", - "After resolving the conflicts, run `bun typecheck` in `packages/opencode`.", + "After resolving the conflicts, run `bun typecheck` at the repo root.", "If typecheck fails, you may also update any files reported by typecheck.", "Keep any non-conflict edits narrowly scoped to restoring a valid merged state for the current PR batch.", "Fix any merge-caused typecheck errors before finishing.", @@ -149,7 +149,7 @@ async function smoke(prs: PR[], applied: number[]) { const prompt = [ "The beta merge batch is complete.", `Merged PRs on HEAD:\n${done}`, - "Run `bun typecheck` in `packages/opencode`.", + "Run `bun typecheck` at the repo root.", "Run `./script/build.ts --single` in `packages/opencode`.", "Fix any merge-caused issues until both commands pass.", "Do not create a commit.", From 97300085437899af8af6c2bbf6ebc6bdab110174 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 23 Apr 2026 00:29:56 -0400 Subject: [PATCH 069/426] tweak: codex model logic (#23925) --- packages/opencode/src/plugin/codex.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index c61cb7850..e05111fc6 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -378,6 +378,8 @@ export async function CodexAuthPlugin(input: PluginInput): Promise { for (const [modelId, model] of Object.entries(provider.models)) { if (modelId.includes("codex")) continue if (allowedModels.has(model.api.id)) continue + const match = model.api.id.match(/^gpt-(\d+\.\d+)/) + if (match && parseFloat(match[1]) > 5.4) continue delete provider.models[modelId] } From df27baa00d0de032a7213b043d3b04f939362baa Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Thu, 23 Apr 2026 13:15:02 +0800 Subject: [PATCH 070/426] refactor: remove redundant pending check from working memo (#23929) --- packages/app/src/pages/session/message-timeline.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index 9e0fed11c..fa57cabae 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -259,7 +259,7 @@ export function MessageTimeline(props: { if (!id) return idle return sync.data.session_status[id] ?? idle }) - const working = createMemo(() => !!pending() || sessionStatus().type !== "idle") + const working = createMemo(() => sessionStatus().type !== "idle") const tint = createMemo(() => messageAgentColor(sessionMessages(), sync.data.agent)) const [timeoutDone, setTimeoutDone] = createSignal(true) From 871789ce64f179d5efa3031afbce3789e27e99b5 Mon Sep 17 00:00:00 2001 From: opencode Date: Thu, 23 Apr 2026 05:45:21 +0000 Subject: [PATCH 071/426] sync release versions for v1.14.21 --- bun.lock | 32 +++++++++++++------------- 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-electron/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/shared/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index 64b32feac..addecfff2 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -83,7 +83,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -117,7 +117,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -144,7 +144,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -168,7 +168,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -192,7 +192,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -225,7 +225,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -269,7 +269,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -298,7 +298,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -314,7 +314,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.14.20", + "version": "1.14.21", "bin": { "opencode": "./bin/opencode", }, @@ -459,7 +459,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -494,7 +494,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "cross-spawn": "catalog:", }, @@ -509,7 +509,7 @@ }, "packages/shared": { "name": "@opencode-ai/shared", - "version": "1.14.20", + "version": "1.14.21", "bin": { "opencode": "./bin/opencode", }, @@ -533,7 +533,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -568,7 +568,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -617,7 +617,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.14.20", + "version": "1.14.21", "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 f461459fc..7572f0b22 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.14.20", + "version": "1.14.21", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index d8c9434bf..1182f1794 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.14.20", + "version": "1.14.21", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 090e5c59d..f79ef3ecb 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.14.20", + "version": "1.14.21", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index a2e0c5f03..c3c549004 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.14.20", + "version": "1.14.21", "$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 7aa0cb757..f4c59c196 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.14.20", + "version": "1.14.21", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 155e43f5e..3f5ca841b 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.14.20", + "version": "1.14.21", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index a3b6eac6b..0e349f81c 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.14.20", + "version": "1.14.21", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 016863650..77b0ec200 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.14.20", + "version": "1.14.21", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index cee69b11a..5a9d2dc1a 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.14.20" +version = "1.14.21" 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.14.20/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.20/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.20/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/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.14.20/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/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.14.20/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 4e920b688..925e458cf 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.14.20", + "version": "1.14.21", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index dcd0a9ad6..cd9c0f0a8 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.20", + "version": "1.14.21", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 64d12eb42..d71901066 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.14.20", + "version": "1.14.21", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index a6ba6ee9f..3d07efef4 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.14.20", + "version": "1.14.21", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/shared/package.json b/packages/shared/package.json index 22a8c9c07..117c12b98 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.20", + "version": "1.14.21", "name": "@opencode-ai/shared", "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index 7a016907a..2eb380dc7 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.14.20", + "version": "1.14.21", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index dcf57345e..a15918dc8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.14.20", + "version": "1.14.21", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 8fd33564d..9924f2e51 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.14.20", + "version": "1.14.21", "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 720c70196..a43348670 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.14.20", + "version": "1.14.21", "publisher": "sst-dev", "repository": { "type": "git", From a419f1c50f4efaf2119991eee1d9bc2eef78cb8f Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 23 Apr 2026 02:56:41 -0400 Subject: [PATCH 072/426] zen: hy3 preview --- packages/web/src/content/docs/ar/zen.mdx | 8 ++++++-- packages/web/src/content/docs/bs/zen.mdx | 8 ++++++-- packages/web/src/content/docs/da/zen.mdx | 8 ++++++-- packages/web/src/content/docs/de/zen.mdx | 8 ++++++-- packages/web/src/content/docs/es/zen.mdx | 8 ++++++-- packages/web/src/content/docs/fr/zen.mdx | 8 ++++++-- packages/web/src/content/docs/it/zen.mdx | 8 ++++++-- packages/web/src/content/docs/ja/zen.mdx | 8 ++++++-- packages/web/src/content/docs/ko/zen.mdx | 8 ++++++-- packages/web/src/content/docs/nb/zen.mdx | 8 ++++++-- packages/web/src/content/docs/pl/zen.mdx | 8 ++++++-- packages/web/src/content/docs/pt-br/zen.mdx | 8 ++++++-- packages/web/src/content/docs/ru/zen.mdx | 8 ++++++-- packages/web/src/content/docs/th/zen.mdx | 8 ++++++-- packages/web/src/content/docs/tr/zen.mdx | 8 ++++++-- packages/web/src/content/docs/zen.mdx | 8 ++++++-- packages/web/src/content/docs/zh-cn/zen.mdx | 8 ++++++-- packages/web/src/content/docs/zh-tw/zen.mdx | 8 ++++++-- 18 files changed, 108 insertions(+), 36 deletions(-) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index c30cf3287..f85c15ea9 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -85,8 +85,8 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -96,6 +96,7 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | يستخدم [معرّف النموذج](/docs/config/#models) في إعدادات OpenCode الصيغة `opencode/`. على سبيل المثال، بالنسبة إلى GPT 5.4، ستستخدم `opencode/gpt-5.4` في إعداداتك. @@ -121,6 +122,7 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -170,6 +172,7 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Ling 2.6 Flash Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. +- Hy3 Preview Flash Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Nemotron 3 Super Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Big Pickle نموذج خفي ومتاح مجانا على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. @@ -214,6 +217,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - MiniMax M2.5 Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - Ling 2.6 Flash Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. +- Hy3 Preview Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - Nemotron 3 Super Free (نقاط نهاية NVIDIA المجانية): يُقدَّم بموجب [شروط خدمة النسخة التجريبية من واجهة NVIDIA API](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). للاستخدام التجريبي فقط، وليس للإنتاج أو البيانات الحساسة. تقوم NVIDIA بتسجيل المطالبات والمخرجات لتحسين نماذجها وخدماتها. لا ترسل بيانات شخصية أو سرية. - OpenAI APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index b6fc90fa0..026e145ad 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -90,8 +90,8 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -101,6 +101,7 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) u vašoj OpenCode konfiguraciji koristi format @@ -128,6 +129,7 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -177,6 +179,7 @@ Besplatni modeli: - MiniMax M2.5 Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Ling 2.6 Flash Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. +- Hy3 Preview Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Nemotron 3 Super Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Big Pickle je stealth model koji je besplatan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. @@ -226,6 +229,7 @@ i ne koriste vaše podatke za treniranje modela, uz sljedeće izuzetke: - Big Pickle: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - MiniMax M2.5 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - Ling 2.6 Flash Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. +- Hy3 Preview Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - Nemotron 3 Super Free (besplatni NVIDIA endpointi): Dostupan je prema [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Samo za probnu upotrebu, nije za produkciju niti osjetljive podatke. NVIDIA bilježi promptove i izlaze radi poboljšanja svojih modela i usluga. Nemojte slati lične ili povjerljive podatke. - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index ea5189ec2..b5c9f469f 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -90,8 +90,8 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -101,6 +101,7 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) i din OpenCode-konfiguration @@ -128,6 +129,7 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -177,6 +179,7 @@ De gratis modeller: - MiniMax M2.5 Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Ling 2.6 Flash Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. +- Hy3 Preview Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Nemotron 3 Super Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Big Pickle er en stealth-model, som er gratis på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. @@ -224,6 +227,7 @@ Alle vores modeller hostes i US. Vores udbydere følger en nul-opbevaringspoliti - Big Pickle: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - MiniMax M2.5 Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - Ling 2.6 Flash Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. +- Hy3 Preview Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - Nemotron 3 Super Free (gratis NVIDIA-endpoints): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun til prøvebrug, ikke til produktion eller følsomme data. Prompts og outputs logges af NVIDIA for at forbedre deres modeller og tjenester. Indsend ikke personlige eller fortrolige data. - OpenAI APIs: Anmodninger opbevares i 30 dage i overensstemmelse med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Anmodninger opbevares i 30 dage i overensstemmelse med [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index b8a09497c..2fd012dd1 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -81,8 +81,8 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -92,6 +92,7 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | Die [Model-ID](/docs/config/#models) in deiner OpenCode-Konfiguration verwendet das Format `opencode/`. Für GPT 5.4 würdest du zum Beispiel `opencode/gpt-5.4` in deiner Konfiguration verwenden. @@ -117,6 +118,7 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -166,6 +168,7 @@ Die kostenlosen Modelle: - MiniMax M2.5 Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Ling 2.6 Flash Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. +- Hy3 Preview Flash Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Nemotron 3 Super Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Big Pickle ist ein Stealth-Modell, das für begrenzte Zeit kostenlos auf OpenCode verfügbar ist. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. @@ -210,6 +213,7 @@ Alle unsere Modelle werden in den USA gehostet. Unsere Provider folgen einer Zer - Big Pickle: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - MiniMax M2.5 Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - Ling 2.6 Flash Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. +- Hy3 Preview Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - Nemotron 3 Super Free (kostenlose NVIDIA-Endpunkte): Bereitgestellt gemäß den [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Nur für Testzwecke, nicht für Produktion oder sensible Daten. Eingaben und Ausgaben werden von NVIDIA protokolliert, um seine Modelle und Dienste zu verbessern. Übermitteln Sie keine personenbezogenen oder vertraulichen Daten. - OpenAI APIs: Anfragen werden in Übereinstimmung mit [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 30 Tage lang gespeichert. - Anthropic APIs: Anfragen werden in Übereinstimmung mit [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 30 Tage lang gespeichert. diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index b8999a050..82ed07a23 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -90,8 +90,8 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -101,6 +101,7 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | El [identificador del modelo](/docs/config/#models) en tu configuración de OpenCode @@ -128,6 +129,7 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -177,6 +179,7 @@ Los modelos gratuitos: - MiniMax M2.5 Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Ling 2.6 Flash Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. +- Hy3 Preview Flash Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Nemotron 3 Super Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Big Pickle es un modelo stealth que es gratuito en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. @@ -224,6 +227,7 @@ Todos nuestros modelos están alojados en US. Nuestros proveedores siguen una po - Big Pickle: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - MiniMax M2.5 Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - Ling 2.6 Flash Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. +- Hy3 Preview Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - Nemotron 3 Super Free (endpoints gratuitos de NVIDIA): Se ofrece bajo los [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo para uso de prueba, no para producción ni datos sensibles. NVIDIA registra los prompts y las salidas para mejorar sus modelos y servicios. No envíes datos personales ni confidenciales. - OpenAI APIs: Las solicitudes se conservan durante 30 días de acuerdo con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Las solicitudes se conservan durante 30 días de acuerdo con [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index ab759f053..0b2bcf94a 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -81,8 +81,8 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -92,6 +92,7 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | Le [model id](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode/`. Par exemple, pour GPT 5.4, vous utiliseriez `opencode/gpt-5.4` dans votre configuration. @@ -117,6 +118,7 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -166,6 +168,7 @@ Les modèles gratuits : - MiniMax M2.5 Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Ling 2.6 Flash Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. +- Hy3 Preview Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Nemotron 3 Super Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Big Pickle est un modèle stealth gratuit sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. @@ -210,6 +213,7 @@ Tous nos modèles sont hébergés aux US. Nos fournisseurs suivent une politique - Big Pickle : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - MiniMax M2.5 Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - Ling 2.6 Flash Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. +- Hy3 Preview Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - Nemotron 3 Super Free (endpoints NVIDIA gratuits) : Fourni dans le cadre des [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Réservé à un usage d'essai, pas à la production ni aux données sensibles. Les prompts et les sorties sont journalisés par NVIDIA pour améliorer ses modèles et services. N'envoyez pas de données personnelles ou confidentielles. - OpenAI APIs : Les requêtes sont conservées pendant 30 jours conformément à [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs : Les requêtes sont conservées pendant 30 jours conformément à [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 38c69b021..14ddb891d 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -90,8 +90,8 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -101,6 +101,7 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | Il [model id](/docs/config/#models) nella config di OpenCode @@ -128,6 +129,7 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -177,6 +179,7 @@ I modelli gratuiti: - MiniMax M2.5 Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Ling 2.6 Flash Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. +- Hy3 Preview Flash Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Nemotron 3 Super Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Big Pickle è un modello stealth che è gratuito su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. @@ -224,6 +227,7 @@ Tutti i nostri modelli sono ospitati negli US. I nostri provider seguono una pol - Big Pickle: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - MiniMax M2.5 Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - Ling 2.6 Flash Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. +- Hy3 Preview Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - Nemotron 3 Super Free (endpoint NVIDIA gratuiti): fornito secondo i [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo per uso di prova, non per produzione o dati sensibili. NVIDIA registra prompt e output per migliorare i propri modelli e servizi. Non inviare dati personali o riservati. - OpenAI APIs: le richieste vengono conservate per 30 giorni in conformità con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: le richieste vengono conservate per 30 giorni in conformità con [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index 8f95473d6..ee84dc917 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -81,8 +81,8 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -92,6 +92,7 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode 設定で使う [model id](/docs/config/#models) は `opencode/` 形式です。たとえば、GPT 5.4 では設定に `opencode/gpt-5.4` を使用します。 @@ -117,6 +118,7 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -166,6 +168,7 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Ling 2.6 Flash Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 +- Hy3 Preview Flash Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Nemotron 3 Super Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Big Pickle はステルスモデルで、期間限定で OpenCode で無料提供されています。チームはこの期間中にフィードバックを集め、モデルを改善しています。 @@ -210,6 +213,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - MiniMax M2.5 Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - Ling 2.6 Flash Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 +- Hy3 Preview Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - Nemotron 3 Super Free(NVIDIA の無料エンドポイント): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) に基づいて提供されます。試用専用であり、本番環境や機密性の高いデータには使用しないでください。プロンプトと出力は、NVIDIA が自社のモデルとサービスを改善するために記録します。個人情報や機密データは送信しないでください。 - OpenAI APIs: リクエストは [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) に従って 30 日間保持されます。 - Anthropic APIs: リクエストは [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) に従って 30 日間保持されます。 diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 59d744806..416ec9f20 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -81,8 +81,8 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -92,6 +92,7 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode config에서 사용하는 [모델 ID](/docs/config/#models)는 `opencode/` 형식입니다. 예를 들어 GPT 5.4를 사용하려면 config에서 `opencode/gpt-5.4`를 사용하면 됩니다. @@ -117,6 +118,7 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -166,6 +168,7 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Ling 2.6 Flash Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. +- Hy3 Preview Flash Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Nemotron 3 Super Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Big Pickle은 한정된 기간 동안 OpenCode에서 무료로 제공되는 stealth model입니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. @@ -210,6 +213,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - MiniMax M2.5 Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - Ling 2.6 Flash Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. +- Hy3 Preview Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - Nemotron 3 Super Free(NVIDIA 무료 엔드포인트): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)에 따라 제공됩니다. 평가판 전용이며 프로덕션 환경이나 민감한 데이터에는 사용할 수 없습니다. NVIDIA는 자사 모델과 서비스를 개선하기 위해 프롬프트와 출력을 기록합니다. 개인 정보나 기밀 데이터는 제출하지 마세요. - OpenAI APIs: 요청은 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data)에 따라 30일 동안 보관됩니다. - Anthropic APIs: 요청은 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage)에 따라 30일 동안 보관됩니다. diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index 15c942732..25689ef8c 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -90,8 +90,8 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -101,6 +101,7 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [modell-id](/docs/config/#models) i OpenCode-konfigurasjonen din @@ -128,6 +129,7 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -177,6 +179,7 @@ Gratis-modellene: - MiniMax M2.5 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Ling 2.6 Flash Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. +- Hy3 Preview Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Nemotron 3 Super Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Big Pickle er en stealth-modell som er gratis på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. @@ -224,6 +227,7 @@ Alle modellene våre hostes i US. Leverandørene våre følger en policy for zer - Big Pickle: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - MiniMax M2.5 Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - Ling 2.6 Flash Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. +- Hy3 Preview Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - Nemotron 3 Super Free (gratis NVIDIA-endepunkter): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun for prøvebruk, ikke for produksjon eller sensitive data. Prompter og svar logges av NVIDIA for å forbedre modellene og tjenestene deres. Ikke send inn personopplysninger eller konfidensielle data. - OpenAI APIs: Forespørsler lagres i 30 dager i samsvar med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Forespørsler lagres i 30 dager i samsvar med [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 483a3cedf..023f22da2 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -90,8 +90,8 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -101,6 +101,7 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [ID modelu](/docs/config/#models) w Twojej konfiguracji OpenCode używa formatu @@ -128,6 +129,7 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -178,6 +180,7 @@ Darmowe modele: - MiniMax M2.5 Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Ling 2.6 Flash Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. +- Hy3 Preview Flash Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Nemotron 3 Super Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Big Pickle to stealth model, który jest darmowy w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. @@ -225,6 +228,7 @@ Wszystkie nasze modele są hostowane w US. Nasi dostawcy stosują politykę zero - Big Pickle: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - MiniMax M2.5 Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - Ling 2.6 Flash Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. +- Hy3 Preview Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - Nemotron 3 Super Free (darmowe endpointy NVIDIA): Udostępniany zgodnie z [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Tylko do użytku próbnego, nie do produkcji ani danych wrażliwych. NVIDIA rejestruje prompty i odpowiedzi, aby ulepszać swoje modele i usługi. Nie przesyłaj danych osobowych ani poufnych. - OpenAI APIs: Żądania są przechowywane przez 30 dni zgodnie z [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Żądania są przechowywane przez 30 dni zgodnie z [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index 1112066e2..05fabd40c 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -81,8 +81,8 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -92,6 +92,7 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | O [model id](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode/`. Por exemplo, para GPT 5.4, você usaria `opencode/gpt-5.4` na sua configuração. @@ -117,6 +118,7 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -166,6 +168,7 @@ Os modelos gratuitos: - MiniMax M2.5 Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Ling 2.6 Flash Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. +- Hy3 Preview Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Nemotron 3 Super Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Big Pickle é um modelo stealth que está gratuito no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. @@ -210,6 +213,7 @@ Todos os nossos modelos são hospedados nos US. Nossos provedores seguem uma pol - Big Pickle: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - MiniMax M2.5 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - Ling 2.6 Flash Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. +- Hy3 Preview Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - Nemotron 3 Super Free (endpoints gratuitos da NVIDIA): Fornecido sob os [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Apenas para uso de avaliação, não para produção nem dados sensíveis. A NVIDIA registra prompts e saídas para melhorar seus modelos e serviços. Não envie dados pessoais ou confidenciais. - OpenAI APIs: As solicitações são retidas por 30 dias de acordo com [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: As solicitações são retidas por 30 dias de acordo com [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 27d81354e..13184ecf7 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -90,8 +90,8 @@ OpenCode Zen работает как любой другой провайдер | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -101,6 +101,7 @@ OpenCode Zen работает как любой другой провайдер | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [идентификатор модели](/docs/config/#models) в вашей конфигурации OpenCode @@ -128,6 +129,7 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -177,6 +179,7 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Ling 2.6 Flash Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. +- Hy3 Preview Flash Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Nemotron 3 Super Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Big Pickle — это скрытая модель, которая доступна бесплатно в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. @@ -224,6 +227,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - MiniMax M2.5 Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - Ling 2.6 Flash Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. +- Hy3 Preview Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - Nemotron 3 Super Free (бесплатные эндпоинты NVIDIA): предоставляется в соответствии с [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Только для пробного использования, не для продакшена и не для чувствительных данных. NVIDIA логирует запросы и ответы, чтобы улучшать свои модели и сервисы. Не отправляйте персональные или конфиденциальные данные. - OpenAI APIs: запросы хранятся 30 дней в соответствии с [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: запросы хранятся 30 дней в соответствии с [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index 78de61a9b..b75e6d5fd 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -83,8 +83,8 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -94,6 +94,7 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) ใน OpenCode config ของคุณใช้รูปแบบ `opencode/` ตัวอย่างเช่น สำหรับ GPT 5.4 คุณจะใช้ `opencode/gpt-5.4` ใน config ของคุณ @@ -119,6 +120,7 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -168,6 +170,7 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Ling 2.6 Flash Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล +- Hy3 Preview Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Nemotron 3 Super Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Big Pickle เป็น stealth model ที่ใช้งานฟรีบน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล @@ -212,6 +215,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - MiniMax M2.5 Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - Ling 2.6 Flash Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล +- Hy3 Preview Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - Nemotron 3 Super Free (endpoint ฟรีของ NVIDIA): ให้บริการภายใต้ [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) ใช้สำหรับการทดลองเท่านั้น ไม่เหมาะสำหรับ production หรือข้อมูลที่อ่อนไหว NVIDIA จะบันทึก prompt และ output เพื่อนำไปปรับปรุงโมเดลและบริการของตน โปรดอย่าส่งข้อมูลส่วนบุคคลหรือข้อมูลลับ. - OpenAI APIs: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index 409c11a21..a3a1781f0 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -81,8 +81,8 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -92,6 +92,7 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode yapılandırmanızdaki [model id](/docs/config/#models) `opencode/` biçimini kullanır. Örneğin, GPT 5.4 için yapılandırmanızda `opencode/gpt-5.4` kullanırsınız. @@ -117,6 +118,7 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -166,6 +168,7 @@ Kredi kartı ücretleri maliyet üzerinden yansıtılır (%4.4 + işlem başına - MiniMax M2.5 Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Ling 2.6 Flash Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. +- Hy3 Preview Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Nemotron 3 Super Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Big Pickle, sınırlı bir süre için OpenCode'da ücretsiz olan gizli bir modeldir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. @@ -210,6 +213,7 @@ Tüm modellerimiz US'de barındırılıyor. Sağlayıcılarımız zero-retention - Big Pickle: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - MiniMax M2.5 Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - Ling 2.6 Flash Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. +- Hy3 Preview Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - Nemotron 3 Super Free (ücretsiz NVIDIA uç noktaları): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) kapsamında sunulur. Yalnızca deneme amaçlıdır; üretim veya hassas veriler için uygun değildir. NVIDIA, modellerini ve hizmetlerini geliştirmek için promptları ve çıktıları kaydeder. Kişisel veya gizli veri göndermeyin. - OpenAI APIs: İstekler [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) uyarınca 30 gün boyunca saklanır. - Anthropic APIs: İstekler [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) uyarınca 30 gün boyunca saklanır. diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 1c59c00f7..d208d3ae5 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -90,8 +90,8 @@ You can also access our models through the following API endpoints. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -101,6 +101,7 @@ You can also access our models through the following API endpoints. | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | The [model id](/docs/config/#models) in your OpenCode config @@ -128,6 +129,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -177,6 +179,7 @@ The free models: - MiniMax M2.5 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Ling 2.6 Flash Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. +- Hy3 Preview Flash Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Nemotron 3 Super Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. @@ -224,6 +227,7 @@ All our models are hosted in the US. Our providers follow a zero-retention polic - Big Pickle: During its free period, collected data may be used to improve the model. - MiniMax M2.5 Free: During its free period, collected data may be used to improve the model. - Ling 2.6 Flash Free: During its free period, collected data may be used to improve the model. +- Hy3 Preview Free: During its free period, collected data may be used to improve the model. - Nemotron 3 Super Free (NVIDIA free endpoints): Provided under the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Trial use only — not for production or sensitive data. Prompts and outputs are logged by NVIDIA to improve its models and services. Do not submit personal or confidential data. - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index 017c5b6c9..510b93666 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -81,8 +81,8 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -92,6 +92,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | 在你的 OpenCode 配置中,[模型 ID](/docs/config/#models) 使用 `opencode/` 格式。例如,对于 GPT 5.4,你需要在配置中使用 `opencode/gpt-5.4`。 @@ -117,6 +118,7 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -166,6 +168,7 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Ling 2.6 Flash Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 +- Hy3 Preview Flash Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Nemotron 3 Super Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Big Pickle 是一个隐身模型,目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 @@ -210,6 +213,7 @@ https://opencode.ai/zen/v1/models - Big Pickle:在免费期间,收集的数据可能会被用于改进模型。 - MiniMax M2.5 Free:在免费期间,收集的数据可能会被用于改进模型。 - Ling 2.6 Flash Free:在免费期间,收集的数据可能会被用于改进模型。 +- Hy3 Preview Free:在免费期间,收集的数据可能会被用于改进模型。 - Nemotron 3 Super Free(NVIDIA 免费端点):根据 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。仅供试用,不适用于生产环境或敏感数据。NVIDIA 会记录提示词和输出内容,以改进其模型和服务。请勿提交个人或机密数据。 - OpenAI APIs:请求会根据 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。 - Anthropic APIs:请求会根据 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 保留 30 天。 diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 0afaf10b6..3634b6eca 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -85,8 +85,8 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -96,6 +96,7 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode 設定中的 [模型 ID](/docs/config/#models) 會使用 `opencode/` @@ -122,6 +123,7 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | Ling 2.6 Flash Free | Free | Free | Free | - | +| Hy3 Preview Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -172,6 +174,7 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Ling 2.6 Flash Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 +- Hy3 Preview Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Nemotron 3 Super Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Big Pickle 是一個隱身模型,在 OpenCode 上限時免費提供。團隊正在利用這段時間收集回饋並改進模型。 @@ -217,6 +220,7 @@ https://opencode.ai/zen/v1/models - Big Pickle: 在免費期間,收集到的資料可能會用於改進模型。 - MiniMax M2.5 Free: 在免費期間,收集到的資料可能會用於改進模型。 - Ling 2.6 Flash Free: 在免費期間,收集到的資料可能會用於改進模型。 +- Hy3 Preview Free: 在免費期間,收集到的資料可能會用於改進模型。 - Nemotron 3 Super Free(NVIDIA 免費端點):依據 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。僅供試用,不適用於正式環境或敏感資料。NVIDIA 會記錄提示詞與輸出內容,以改進其模型與服務。請勿提交個人或機密資料。 - OpenAI APIs: 請求會依據 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。 - Anthropic APIs: 請求會依據 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 保留 30 天。 From 785f3589abb5b43d4e7d6d27e308188d961787de Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:32:01 +0800 Subject: [PATCH 073/426] fix: add keyed prop to Show components for proper reactivity (#23935) --- packages/app/src/pages/layout/sidebar-items.tsx | 4 ++-- packages/app/src/pages/session/message-timeline.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 5170311a7..4c36eefa5 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -269,10 +269,10 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
    - + {(child) => (
    - +
    )}
    diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index fa57cabae..592ca774e 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -812,7 +812,7 @@ export function MessageTimeline(props: {
    - + {(id) => (
    @@ -878,12 +878,12 @@ export function MessageTimeline(props: { - void archiveSession(id())}> + void archiveSession(id)}> {language.t("common.archive")} dialog.show(() => )} + onSelect={() => dialog.show(() => )} > {language.t("common.delete")} From 6002500bc0a65aab3da7310797b5498ac0dae18c Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:57:04 +0800 Subject: [PATCH 074/426] feat(project): add icon_url_override field to projects (#23955) --- packages/app/src/context/global-sync.tsx | 35 +- .../src/context/global-sync/child-store.ts | 8 +- packages/app/src/context/layout.tsx | 34 +- .../app/src/pages/layout/sidebar-items.tsx | 4 +- .../migration.sql | 2 + .../snapshot.json | 1481 +++++++++++++++++ packages/opencode/src/project/project.sql.ts | 1 + packages/opencode/src/project/project.ts | 11 +- .../opencode/src/storage/json-migration.ts | 1 + .../opencode/test/project/project.test.ts | 45 +- 10 files changed, 1545 insertions(+), 77 deletions(-) create mode 100644 packages/opencode/migration/20260423070820_add_icon_url_override/migration.sql create mode 100644 packages/opencode/migration/20260423070820_add_icon_url_override/snapshot.json diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 7c819918c..b742667d7 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -10,9 +10,8 @@ import type { import { showToast } from "@opencode-ai/ui/toast" import { getFilename } from "@opencode-ai/shared/util/path" import { batch, createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js" -import { createStore, produce, reconcile, unwrap } from "solid-js/store" +import { createStore, produce, reconcile } from "solid-js/store" import { useLanguage } from "@/context/language" -import { Persist, persisted } from "@/utils/persist" import type { InitError } from "../pages/error" import { useGlobalSDK } from "./global-sdk" import { bootstrapDirectory, bootstrapGlobal, clearProviderRev } from "./global-sync/bootstrap" @@ -24,7 +23,6 @@ import { estimateRootSessionTotal, loadRootSessionsWithFallback } from "./global import { trimSessions } from "./global-sync/session-trim" import type { ProjectMeta } from "./global-sync/types" import { SESSION_RECENT_LIMIT } from "./global-sync/types" -import { sanitizeProject } from "./global-sync/utils" import { formatServerError } from "@/utils/server-errors" import { queryOptions, skipToken, useQueryClient } from "@tanstack/solid-query" @@ -56,15 +54,10 @@ function createGlobalSync() { const sessionLoads = new Map>() const sessionMeta = new Map() - const [projectCache, setProjectCache, projectInit] = persisted( - Persist.global("globalSync.project", ["globalSync.project.v1"]), - createStore({ value: [] as Project[] }), - ) - const [globalStore, setGlobalStore] = createStore({ ready: false, path: { state: "", config: "", worktree: "", directory: "", home: "" }, - project: projectCache.value, + project: [], session_todo: {}, provider: { all: [], connected: [], default: {} }, provider_auth: {}, @@ -73,32 +66,18 @@ function createGlobalSync() { }) const queryClient = useQueryClient() - let active = true - let projectWritten = false let bootedAt = 0 let bootingRoot = false let eventFrame: number | undefined let eventTimer: ReturnType | undefined - onCleanup(() => { - active = false - }) onCleanup(() => { if (eventFrame !== undefined) cancelAnimationFrame(eventFrame) if (eventTimer !== undefined) clearTimeout(eventTimer) }) - const cacheProjects = () => { - setProjectCache( - "value", - untrack(() => globalStore.project.map(sanitizeProject)), - ) - } - const setProjects = (next: Project[] | ((draft: Project[]) => Project[])) => { - projectWritten = true setGlobalStore("project", next) - cacheProjects() } const setBootStore = ((...input: unknown[]) => { @@ -117,16 +96,6 @@ function createGlobalSync() { return (setGlobalStore as (...args: unknown[]) => unknown)(...input) }) as typeof setGlobalStore - if (projectInit instanceof Promise) { - void projectInit.then(() => { - if (!active) return - if (projectWritten) return - const cached = projectCache.value - if (cached.length === 0) return - setGlobalStore("project", cached) - }) - } - const setSessionTodo = (sessionID: string, todos: Todo[] | undefined) => { if (!sessionID) return if (!todos) { diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index c92d2ae57..f3b613a7f 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -156,13 +156,12 @@ export function createChildStoreManager(input: { const init = () => createRoot((dispose) => { - const initialMeta = meta[0].value const initialIcon = icon[0].value const pathQuery = useQuery(() => loadPathQuery(directory)) const child = createStore({ project: "", - projectMeta: initialMeta, + projectMeta: undefined, icon: initialIcon, provider_ready: false, provider: { all: [], connected: [], default: {} }, @@ -208,11 +207,6 @@ export function createChildStoreManager(input: { child[1]("vcs", (value) => value ?? cached) }) - onPersistedInit(meta[2], () => { - if (child[0].projectMeta !== initialMeta) return - child[1]("projectMeta", meta[0].value) - }) - onPersistedInit(icon[2], () => { if (child[0].icon !== initialIcon) return child[1]("icon", icon[0].value) diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 90e357bd3..97d9cacbb 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -391,37 +391,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( ? globalSync.data.project.find((x) => x.id === projectID) : globalSync.data.project.find((x) => x.worktree === project.worktree) - const local = childStore.projectMeta - const localOverride = - local?.name !== undefined || - local?.commands?.start !== undefined || - local?.icon?.override !== undefined || - local?.icon?.color !== undefined - - const base = { - ...metadata, - ...project, - icon: { - url: metadata?.icon?.url, - override: metadata?.icon?.override ?? childStore.icon, - color: metadata?.icon?.color, - }, - } - - const isGlobal = projectID === "global" || (metadata?.id === undefined && localOverride) - if (!isGlobal) return base - - return { - ...base, - id: base.id ?? "global", - name: local?.name, - commands: local?.commands, - icon: { - url: base.icon?.url, - override: local?.icon?.override, - color: local?.icon?.color, - }, - } + return { ...metadata, ...project } } const roots = createMemo(() => { @@ -516,7 +486,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( } for (const project of projects) { - if (project.icon?.color || project.icon.url) continue + if (project.icon?.color || project.icon?.override || project.icon?.url) continue const worktree = project.worktree const existing = colors[worktree] const color = existing ?? pickAvailableColor(used) diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 4c36eefa5..9a9a1d7fc 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -22,9 +22,7 @@ const OPENCODE_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750" export function getProjectAvatarSource(id?: string, icon?: { color?: string; url?: string; override?: string }) { return id === OPENCODE_PROJECT_ID ? "https://opencode.ai/favicon.svg" - : icon?.color - ? undefined - : icon?.override || icon?.url + : (icon?.override ?? (icon?.color ? undefined : icon?.url)) } export const ProjectIcon = (props: { project: LocalProject; class?: string; notify?: boolean }): JSX.Element => { diff --git a/packages/opencode/migration/20260423070820_add_icon_url_override/migration.sql b/packages/opencode/migration/20260423070820_add_icon_url_override/migration.sql new file mode 100644 index 000000000..e28a1d4e9 --- /dev/null +++ b/packages/opencode/migration/20260423070820_add_icon_url_override/migration.sql @@ -0,0 +1,2 @@ +ALTER TABLE `project` ADD `icon_url_override` text; +UPDATE `project` SET `icon_url_override` = `icon_url` WHERE `icon_url` IS NOT NULL; diff --git a/packages/opencode/migration/20260423070820_add_icon_url_override/snapshot.json b/packages/opencode/migration/20260423070820_add_icon_url_override/snapshot.json new file mode 100644 index 000000000..318a2bb53 --- /dev/null +++ b/packages/opencode/migration/20260423070820_add_icon_url_override/snapshot.json @@ -0,0 +1,1481 @@ +{ + "version": "7", + "dialect": "sqlite", + "id": "66cbe0d7-def0-451b-b88a-7608513a9b44", + "prevIds": [ + "30b928c5-deef-472c-856d-b5b5064bf6d4" + ], + "ddl": [ + { + "name": "account_state", + "entityType": "tables" + }, + { + "name": "account", + "entityType": "tables" + }, + { + "name": "control_account", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "name": "project", + "entityType": "tables" + }, + { + "name": "message", + "entityType": "tables" + }, + { + "name": "part", + "entityType": "tables" + }, + { + "name": "permission", + "entityType": "tables" + }, + { + "name": "session_entry", + "entityType": "tables" + }, + { + "name": "session", + "entityType": "tables" + }, + { + "name": "todo", + "entityType": "tables" + }, + { + "name": "session_share", + "entityType": "tables" + }, + { + "name": "event_sequence", + "entityType": "tables" + }, + { + "name": "event", + "entityType": "tables" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_account_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_org_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": "''", + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "branch", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "extra", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "worktree", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "vcs", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url_override", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_color", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_initialized", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "sandboxes", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "commands", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "message_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "session_entry" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "parent_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "title", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "version", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "share_url", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_additions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_deletions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_files", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_diffs", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "revert", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "permission", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_compacting", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_archived", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "content", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "status", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "priority", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "position", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "secret", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "aggregate_id", + "entityType": "columns", + "table": "event_sequence" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "seq", + "entityType": "columns", + "table": "event_sequence" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "aggregate_id", + "entityType": "columns", + "table": "event" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "seq", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "event" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "event" + }, + { + "columns": [ + "active_account_id" + ], + "tableTo": "account", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "nameExplicit": false, + "name": "fk_account_state_active_account_id_account_id_fk", + "entityType": "fks", + "table": "account_state" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_workspace_project_id_project_id_fk", + "entityType": "fks", + "table": "workspace" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_message_session_id_session_id_fk", + "entityType": "fks", + "table": "message" + }, + { + "columns": [ + "message_id" + ], + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_part_message_id_message_id_fk", + "entityType": "fks", + "table": "part" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_permission_project_id_project_id_fk", + "entityType": "fks", + "table": "permission" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_entry_session_id_session_id_fk", + "entityType": "fks", + "table": "session_entry" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_project_id_project_id_fk", + "entityType": "fks", + "table": "session" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_todo_session_id_session_id_fk", + "entityType": "fks", + "table": "todo" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_share_session_id_session_id_fk", + "entityType": "fks", + "table": "session_share" + }, + { + "columns": [ + "aggregate_id" + ], + "tableTo": "event_sequence", + "columnsTo": [ + "aggregate_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_event_aggregate_id_event_sequence_aggregate_id_fk", + "entityType": "fks", + "table": "event" + }, + { + "columns": [ + "email", + "url" + ], + "nameExplicit": false, + "name": "control_account_pk", + "entityType": "pks", + "table": "control_account" + }, + { + "columns": [ + "session_id", + "position" + ], + "nameExplicit": false, + "name": "todo_pk", + "entityType": "pks", + "table": "todo" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_state_pk", + "table": "account_state", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_pk", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "workspace_pk", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "project_pk", + "table": "project", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "message_pk", + "table": "message", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "part_pk", + "table": "part", + "entityType": "pks" + }, + { + "columns": [ + "project_id" + ], + "nameExplicit": false, + "name": "permission_pk", + "table": "permission", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "session_entry_pk", + "table": "session_entry", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "session_pk", + "table": "session", + "entityType": "pks" + }, + { + "columns": [ + "session_id" + ], + "nameExplicit": false, + "name": "session_share_pk", + "table": "session_share", + "entityType": "pks" + }, + { + "columns": [ + "aggregate_id" + ], + "nameExplicit": false, + "name": "event_sequence_pk", + "table": "event_sequence", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "event_pk", + "table": "event", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + }, + { + "value": "id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "message_session_time_created_id_idx", + "entityType": "indexes", + "table": "message" + }, + { + "columns": [ + { + "value": "message_id", + "isExpression": false + }, + { + "value": "id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_message_id_id_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_session_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_entry_session_idx", + "entityType": "indexes", + "table": "session_entry" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + }, + { + "value": "type", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_entry_session_type_idx", + "entityType": "indexes", + "table": "session_entry" + }, + { + "columns": [ + { + "value": "time_created", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_entry_time_created_idx", + "entityType": "indexes", + "table": "session_entry" + }, + { + "columns": [ + { + "value": "project_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_project_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_workspace_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "parent_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_parent_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "todo_session_idx", + "entityType": "indexes", + "table": "todo" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/opencode/src/project/project.sql.ts b/packages/opencode/src/project/project.sql.ts index efbc400b5..2d486114a 100644 --- a/packages/opencode/src/project/project.sql.ts +++ b/packages/opencode/src/project/project.sql.ts @@ -8,6 +8,7 @@ export const ProjectTable = sqliteTable("project", { vcs: text(), name: text(), icon_url: text(), + icon_url_override: text(), icon_color: text(), ...Timestamps, time_initialized: integer(), diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index d628f87f9..ab60cff7a 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -60,7 +60,13 @@ type Row = typeof ProjectTable.$inferSelect export function fromRow(row: Row): Info { const icon = - row.icon_url || row.icon_color ? { url: row.icon_url ?? undefined, color: row.icon_color ?? undefined } : undefined + row.icon_url || row.icon_url_override || row.icon_color + ? { + url: row.icon_url ?? undefined, + override: row.icon_url_override ?? undefined, + color: row.icon_color ?? undefined, + } + : undefined return { id: row.id, worktree: row.worktree, @@ -289,6 +295,7 @@ export const layer: Layer.Layer< vcs: result.vcs ?? null, name: result.name, icon_url: result.icon?.url, + icon_url_override: result.icon?.override, icon_color: result.icon?.color, time_created: result.time.created, time_updated: result.time.updated, @@ -303,6 +310,7 @@ export const layer: Layer.Layer< vcs: result.vcs ?? null, name: result.name, icon_url: result.icon?.url, + icon_url_override: result.icon?.override, icon_color: result.icon?.color, time_updated: result.time.updated, time_initialized: result.time.initialized, @@ -365,6 +373,7 @@ export const layer: Layer.Layer< .set({ name: input.name, icon_url: input.icon?.url, + icon_url_override: input.icon?.override, icon_color: input.icon?.color, commands: input.commands, time_updated: Date.now(), diff --git a/packages/opencode/src/storage/json-migration.ts b/packages/opencode/src/storage/json-migration.ts index 12133ce43..05588db0f 100644 --- a/packages/opencode/src/storage/json-migration.ts +++ b/packages/opencode/src/storage/json-migration.ts @@ -168,6 +168,7 @@ export async function run(db: SQLiteBunDatabase | NodeSQLiteDatabase { expect(updated).toBeDefined() expect(updated!.icon).toBeUndefined() }) + + test("should not discover favicon when override is set", async () => { + await using tmp = await tmpdir({ git: true }) + const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + + await run((svc) => + svc.update({ + projectID: project.id, + icon: { override: "data:image/png;base64,override" }, + }), + ) + + const updatedProject = await run((svc) => svc.get(project.id)) + if (!updatedProject) throw new Error("Project not found") + + const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) + await Bun.write(path.join(tmp.path, "favicon.png"), pngData) + + await run((svc) => svc.discover(updatedProject)) + + const updated = Project.get(project.id) + expect(updated).toBeDefined() + expect(updated!.icon?.override).toBe("data:image/png;base64,override") + expect(updated!.icon?.url).toBeUndefined() + }) }) describe("Project.update", () => { @@ -332,6 +357,23 @@ describe("Project.update", () => { expect(fromDb?.icon?.color).toBe("#ff0000") }) + test("should update icon override", async () => { + await using tmp = await tmpdir({ git: true }) + const { project } = await run((svc) => svc.fromDirectory(tmp.path)) + + const updated = await run((svc) => + svc.update({ + projectID: project.id, + icon: { override: "data:image/png;base64,abc123" }, + }), + ) + + expect(updated.icon?.override).toBe("data:image/png;base64,abc123") + + const fromDb = Project.get(project.id) + expect(fromDb?.icon?.override).toBe("data:image/png;base64,abc123") + }) + test("should update commands", async () => { await using tmp = await tmpdir({ git: true }) const { project } = await run((svc) => svc.fromDirectory(tmp.path)) @@ -389,13 +431,14 @@ describe("Project.update", () => { svc.update({ projectID: project.id, name: "Multi Update", - icon: { url: "https://example.com/favicon.ico", color: "#00ff00" }, + icon: { url: "https://example.com/favicon.ico", override: "data:image/png;base64,abc123", color: "#00ff00" }, commands: { start: "make start" }, }), ) expect(updated.name).toBe("Multi Update") expect(updated.icon?.url).toBe("https://example.com/favicon.ico") + expect(updated.icon?.override).toBe("data:image/png;base64,abc123") expect(updated.icon?.color).toBe("#00ff00") expect(updated.commands?.start).toBe("make start") }) From 3ae74cb0471b0a642c128d88d14649df673297b3 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 10:58:14 +0000 Subject: [PATCH 075/426] chore: generate --- .../snapshot.json | 144 +++++------------- 1 file changed, 36 insertions(+), 108 deletions(-) diff --git a/packages/opencode/migration/20260423070820_add_icon_url_override/snapshot.json b/packages/opencode/migration/20260423070820_add_icon_url_override/snapshot.json index 318a2bb53..06dae8e44 100644 --- a/packages/opencode/migration/20260423070820_add_icon_url_override/snapshot.json +++ b/packages/opencode/migration/20260423070820_add_icon_url_override/snapshot.json @@ -2,9 +2,7 @@ "version": "7", "dialect": "sqlite", "id": "66cbe0d7-def0-451b-b88a-7608513a9b44", - "prevIds": [ - "30b928c5-deef-472c-856d-b5b5064bf6d4" - ], + "prevIds": ["30b928c5-deef-472c-856d-b5b5064bf6d4"], "ddl": [ { "name": "account_state", @@ -1043,13 +1041,9 @@ "table": "event" }, { - "columns": [ - "active_account_id" - ], + "columns": ["active_account_id"], "tableTo": "account", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "SET NULL", "nameExplicit": false, @@ -1058,13 +1052,9 @@ "table": "account_state" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1073,13 +1063,9 @@ "table": "workspace" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1088,13 +1074,9 @@ "table": "message" }, { - "columns": [ - "message_id" - ], + "columns": ["message_id"], "tableTo": "message", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1103,13 +1085,9 @@ "table": "part" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1118,13 +1096,9 @@ "table": "permission" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1133,13 +1107,9 @@ "table": "session_entry" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1148,13 +1118,9 @@ "table": "session" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1163,13 +1129,9 @@ "table": "todo" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1178,13 +1140,9 @@ "table": "session_share" }, { - "columns": [ - "aggregate_id" - ], + "columns": ["aggregate_id"], "tableTo": "event_sequence", - "columnsTo": [ - "aggregate_id" - ], + "columnsTo": ["aggregate_id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1193,128 +1151,98 @@ "table": "event" }, { - "columns": [ - "email", - "url" - ], + "columns": ["email", "url"], "nameExplicit": false, "name": "control_account_pk", "entityType": "pks", "table": "control_account" }, { - "columns": [ - "session_id", - "position" - ], + "columns": ["session_id", "position"], "nameExplicit": false, "name": "todo_pk", "entityType": "pks", "table": "todo" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "account_state_pk", "table": "account_state", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "account_pk", "table": "account", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "workspace_pk", "table": "workspace", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "project_pk", "table": "project", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "message_pk", "table": "message", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "part_pk", "table": "part", "entityType": "pks" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "nameExplicit": false, "name": "permission_pk", "table": "permission", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "session_entry_pk", "table": "session_entry", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "session_pk", "table": "session", "entityType": "pks" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "nameExplicit": false, "name": "session_share_pk", "table": "session_share", "entityType": "pks" }, { - "columns": [ - "aggregate_id" - ], + "columns": ["aggregate_id"], "nameExplicit": false, "name": "event_sequence_pk", "table": "event_sequence", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "event_pk", "table": "event", @@ -1478,4 +1406,4 @@ } ], "renames": [] -} \ No newline at end of file +} From 9b6db08d2144c33ec34cd88026774f847ec79761 Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Thu, 23 Apr 2026 13:02:40 +0200 Subject: [PATCH 076/426] chore: add to TEAM_MEMBERS (#23975) --- .github/TEAM_MEMBERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/TEAM_MEMBERS b/.github/TEAM_MEMBERS index 22c9a923d..3b8519d3b 100644 --- a/.github/TEAM_MEMBERS +++ b/.github/TEAM_MEMBERS @@ -13,3 +13,4 @@ R44VC0RP rekram1-node RhysSullivan thdxr +simonklee From 38deb0f3eeedb9da68f80b398a694622602162bb Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Thu, 23 Apr 2026 19:54:01 +0530 Subject: [PATCH 077/426] fix(npm): respect npmrc config (#24001) --- packages/opencode/src/npm/index.ts | 38 ++++++++++++++++++++++++++++-- packages/opencode/test/npm.test.ts | 37 +++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index fc8497d20..d6322d548 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -1,8 +1,11 @@ export * as Npm from "." import path from "path" +import { fileURLToPath } from "url" import npa from "npm-package-arg" import semver from "semver" +import Config from "@npmcli/config" +import { definitions, flatten, nerfDarts, shorthands } from "@npmcli/config/lib/definitions/index.js" import { Effect, Schema, Context, Layer, Option, FileSystem } from "effect" import { NodeFileSystem } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/shared/filesystem" @@ -40,12 +43,39 @@ export interface Interface { export class Service extends Context.Service()("@opencode/Npm") {} const illegal = process.platform === "win32" ? new Set(["<", ">", ":", '"', "|", "?", "*"]) : undefined +const npmPath = fileURLToPath(new URL("../..", import.meta.url)) export function sanitize(pkg: string) { if (!illegal) return pkg return Array.from(pkg, (char) => (illegal.has(char) || char.charCodeAt(0) < 32 ? "_" : char)).join("") } +const loadOptions = (dir: string) => + Effect.tryPromise({ + try: async () => { + const config = new Config({ + npmPath, + cwd: dir, + env: { ...process.env }, + argv: [process.execPath, process.execPath], + execPath: process.execPath, + platform: process.platform, + definitions, + flatten, + nerfDarts, + shorthands, + warn: false, + }) + await config.load() + return config.flat + }, + catch: (cause) => + new InstallFailedError({ + cause, + dir, + }), + }) + const resolveEntryPoint = (name: string, dir: string): EntryPoint => { let entrypoint: Option.Option try { @@ -81,7 +111,10 @@ export const layer = Layer.effect( Effect.gen(function* () { yield* flock.acquire(`npm-install:${input.dir}`) const { Arborist } = yield* Effect.promise(() => import("@npmcli/arborist")) + const add = input.add ?? [] + const npmOptions = yield* loadOptions(input.dir) const arborist = new Arborist({ + ...npmOptions, path: input.dir, binLinks: true, progress: false, @@ -91,14 +124,15 @@ export const layer = Layer.effect( return yield* Effect.tryPromise({ try: () => arborist.reify({ - add: input?.add || [], + ...npmOptions, + add, save: true, saveType: "prod", }), catch: (cause) => new InstallFailedError({ cause, - add: input?.add, + add, dir: input.dir, }), }) as Effect.Effect diff --git a/packages/opencode/test/npm.test.ts b/packages/opencode/test/npm.test.ts index 61e3ca6dd..a8ec92c2a 100644 --- a/packages/opencode/test/npm.test.ts +++ b/packages/opencode/test/npm.test.ts @@ -1,7 +1,18 @@ +import fs from "fs/promises" +import path from "path" import { describe, expect, test } from "bun:test" import { Npm } from "../src/npm" +import { tmpdir } from "./fixture/fixture" const win = process.platform === "win32" +const writePackage = (dir: string, pkg: Record) => + Bun.write( + path.join(dir, "package.json"), + JSON.stringify({ + version: "1.0.0", + ...pkg, + }), + ) describe("Npm.sanitize", () => { test("keeps normal scoped package specs unchanged", () => { @@ -16,3 +27,29 @@ describe("Npm.sanitize", () => { expect(Npm.sanitize(spec)).toBe(expected) }) }) + +describe("Npm.install", () => { + test("respects omit from project .npmrc", async () => { + await using tmp = await tmpdir() + + await writePackage(tmp.path, { + name: "fixture", + dependencies: { + "prod-pkg": "file:./prod-pkg", + }, + devDependencies: { + "dev-pkg": "file:./dev-pkg", + }, + }) + await Bun.write(path.join(tmp.path, ".npmrc"), "omit=dev\n") + await fs.mkdir(path.join(tmp.path, "prod-pkg")) + await fs.mkdir(path.join(tmp.path, "dev-pkg")) + await writePackage(path.join(tmp.path, "prod-pkg"), { name: "prod-pkg" }) + await writePackage(path.join(tmp.path, "dev-pkg"), { name: "dev-pkg" }) + + await Npm.install(tmp.path) + + await expect(fs.stat(path.join(tmp.path, "node_modules", "prod-pkg"))).resolves.toBeDefined() + await expect(fs.stat(path.join(tmp.path, "node_modules", "dev-pkg"))).rejects.toThrow() + }) +}) From bbf67d0fff28b8d26d7ffb11c347f519308944b0 Mon Sep 17 00:00:00 2001 From: James Long Date: Thu, 23 Apr 2026 11:24:40 -0400 Subject: [PATCH 078/426] fix(tui): render all non-synthetic text parts of a user message (#24009) --- .../src/cli/cmd/tui/routes/session/index.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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 2f5da1d23..c04e58ace 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -1250,7 +1250,17 @@ function UserMessage(props: { }) { const ctx = use() const local = useLocal() - const text = createMemo(() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0]) + const text = createMemo(() => { + const texts = props.parts + .map((x) => { + if (x.type === "text" && !x.synthetic) { + return x.text + } + return null + }) + .filter(Boolean) + return texts.join("\n\n") + }) const files = createMemo(() => props.parts.flatMap((x) => (x.type === "file" ? [x] : []))) const { theme } = useTheme() const [hover, setHover] = createSignal(false) @@ -1285,7 +1295,7 @@ function UserMessage(props: { backgroundColor={hover() ? theme.backgroundElement : theme.backgroundPanel} flexShrink={0} > - {text()?.text} + {text()} From 0517ab4695e6918a379d1e44cc6cd04a9dc80c06 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 11:30:02 -0400 Subject: [PATCH 079/426] refactor(session): migrate session domain to Effect Schema (#24005) --- packages/opencode/specs/effect/schema.md | 21 +- packages/opencode/src/acp/agent.ts | 4 +- packages/opencode/src/cli/cmd/import.ts | 5 +- .../server/routes/instance/experimental.ts | 2 +- .../src/server/routes/instance/session.ts | 72 ++-- packages/opencode/src/session/message.ts | 353 +++++++++--------- packages/opencode/src/session/projectors.ts | 2 +- packages/opencode/src/session/prompt.ts | 149 ++++---- packages/opencode/src/session/revert.ts | 17 +- packages/opencode/src/session/session.ts | 193 +++++----- packages/opencode/src/session/status.ts | 42 +-- packages/opencode/src/session/summary.ts | 14 +- packages/opencode/src/session/todo.ts | 24 +- packages/opencode/src/tool/todo.ts | 15 +- packages/opencode/src/util/effect-zod.ts | 27 ++ .../test/server/global-session-list.test.ts | 2 +- .../opencode/test/session/compaction.test.ts | 2 +- .../test/session/schema-decoding.test.ts | 310 +++++++++++++++ 18 files changed, 812 insertions(+), 442 deletions(-) create mode 100644 packages/opencode/test/session/schema-decoding.test.ts diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 9ff6859ce..3f2c3b4c9 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -159,6 +159,7 @@ Schema at source. These are the highest-priority next targets. Each is a small, self-contained schema module with a clear domain. +- [x] `src/account/schema.ts` - [x] `src/control-plane/schema.ts` - [x] `src/permission/schema.ts` - [x] `src/project/schema.ts` @@ -166,8 +167,10 @@ schema module with a clear domain. - [x] `src/pty/schema.ts` - [x] `src/question/schema.ts` - [x] `src/session/schema.ts` +- [x] `src/storage/schema.ts` - [x] `src/sync/schema.ts` - [x] `src/tool/schema.ts` +- [x] `src/util/schema.ts` ### Session domain @@ -248,15 +251,15 @@ Possible later tightening after the Schema-first migration is stable: - promote repeated opaque strings and timestamp numbers into branded/newtype leaf schemas where that adds domain value without changing the wire format -- [ ] `src/session/compaction.ts` -- [ ] `src/session/message-v2.ts` -- [ ] `src/session/message.ts` -- [ ] `src/session/prompt.ts` -- [ ] `src/session/revert.ts` -- [ ] `src/session/session.ts` -- [ ] `src/session/status.ts` -- [ ] `src/session/summary.ts` -- [ ] `src/session/todo.ts` +- [x] `src/session/compaction.ts` +- [x] `src/session/message-v2.ts` +- [x] `src/session/message.ts` +- [x] `src/session/prompt.ts` +- [x] `src/session/revert.ts` +- [x] `src/session/session.ts` +- [x] `src/session/status.ts` +- [x] `src/session/summary.ts` +- [x] `src/session/todo.ts` ### Provider domain diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index f12328153..672b93f6c 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -372,7 +372,7 @@ export class Agent implements ACPAgent { } if (part.tool === "todowrite") { - const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output)) + const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output)) if (parsedTodos.success) { await this.connection .sessionUpdate({ @@ -901,7 +901,7 @@ export class Agent implements ACPAgent { } if (part.tool === "todowrite") { - const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output)) + const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output)) if (parsedTodos.success) { await this.connection .sessionUpdate({ diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index 309ec6d95..e52120f1a 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -11,6 +11,7 @@ import { ShareNext } from "../../share" import { EOL } from "os" import { Filesystem } from "../../util" import { AppRuntime } from "@/effect/app-runtime" +import { Schema } from "effect" /** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */ export type ShareData = @@ -154,10 +155,10 @@ export const ImportCommand = cmd({ return } - const info = Session.Info.parse({ + const info = Schema.decodeUnknownSync(Session.Info)({ ...exportData.info, projectID: Instance.project.id, - }) + }) as Session.Info const row = Session.toRow(info) Database.use((db) => db diff --git a/packages/opencode/src/server/routes/instance/experimental.ts b/packages/opencode/src/server/routes/instance/experimental.ts index 9c8649498..93a5d98c9 100644 --- a/packages/opencode/src/server/routes/instance/experimental.ts +++ b/packages/opencode/src/server/routes/instance/experimental.ts @@ -335,7 +335,7 @@ export const ExperimentalRoutes = lazy(() => description: "List of sessions", content: { "application/json": { - schema: resolver(Session.GlobalInfo.array()), + schema: resolver(Session.GlobalInfo.zod.array()), }, }, }, diff --git a/packages/opencode/src/server/routes/instance/session.ts b/packages/opencode/src/server/routes/instance/session.ts index 8d0302426..4f4f8ed86 100644 --- a/packages/opencode/src/server/routes/instance/session.ts +++ b/packages/opencode/src/server/routes/instance/session.ts @@ -23,6 +23,7 @@ import { PermissionID } from "@/permission/schema" import { ModelID, ProviderID } from "@/provider/schema" import { errors } from "../../error" import { lazy } from "@/util/lazy" +import { zodObject } from "@/util/effect-zod" import { Bus } from "@/bus" import { NamedError } from "@opencode-ai/shared/util/error" import { jsonRequest, runRequest } from "./trace" @@ -42,7 +43,7 @@ export const SessionRoutes = lazy(() => description: "List of sessions", content: { "application/json": { - schema: resolver(Session.Info.array()), + schema: resolver(Session.Info.zod.array()), }, }, }, @@ -87,7 +88,7 @@ export const SessionRoutes = lazy(() => description: "Get session status", content: { "application/json": { - schema: resolver(z.record(z.string(), SessionStatus.Info)), + schema: resolver(z.record(z.string(), SessionStatus.Info.zod)), }, }, }, @@ -112,7 +113,7 @@ export const SessionRoutes = lazy(() => description: "Get session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -122,7 +123,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: Session.GetInput, + sessionID: Session.GetInput.zod, }), ), async (c) => { @@ -145,7 +146,7 @@ export const SessionRoutes = lazy(() => description: "List of children", content: { "application/json": { - schema: resolver(Session.Info.array()), + schema: resolver(Session.Info.zod.array()), }, }, }, @@ -155,7 +156,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: Session.ChildrenInput, + sessionID: Session.ChildrenInput.zod, }), ), async (c) => { @@ -177,7 +178,7 @@ export const SessionRoutes = lazy(() => description: "Todo list", content: { "application/json": { - schema: resolver(Todo.Info.array()), + schema: resolver(Todo.Info.zod.array()), }, }, }, @@ -210,13 +211,13 @@ export const SessionRoutes = lazy(() => description: "Successfully created session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, }, }), - validator("json", Session.CreateInput), + validator("json", Session.CreateInput.zod), async (c) => jsonRequest("SessionRoutes.create", c, function* () { const body = c.req.valid("json") ?? {} @@ -245,7 +246,7 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: Session.RemoveInput, + sessionID: Session.RemoveInput.zod, }), ), async (c) => @@ -267,7 +268,7 @@ export const SessionRoutes = lazy(() => description: "Successfully updated session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -375,7 +376,7 @@ export const SessionRoutes = lazy(() => description: "200", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -384,14 +385,14 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: Session.ForkInput.shape.sessionID, + sessionID: SessionID.zod, }), ), - validator("json", Session.ForkInput.omit({ sessionID: true })), + validator("json", zodObject(Session.ForkInput).omit({ sessionID: true })), async (c) => jsonRequest("SessionRoutes.fork", c, function* () { const sessionID = c.req.valid("param").sessionID - const body = c.req.valid("json") + const body = c.req.valid("json") as { messageID?: MessageID } const svc = yield* Session.Service return yield* svc.fork({ ...body, sessionID }) }), @@ -438,7 +439,7 @@ export const SessionRoutes = lazy(() => description: "Successfully shared session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -480,18 +481,13 @@ export const SessionRoutes = lazy(() => validator( "param", z.object({ - sessionID: SessionSummary.DiffInput.shape.sessionID, - }), - ), - validator( - "query", - z.object({ - messageID: SessionSummary.DiffInput.shape.messageID, + sessionID: SessionID.zod, }), ), + validator("query", zodObject(SessionSummary.DiffInput).omit({ sessionID: true })), async (c) => jsonRequest("SessionRoutes.diff", c, function* () { - const query = c.req.valid("query") + const query = c.req.valid("query") as Omit const params = c.req.valid("param") const summary = yield* SessionSummary.Service return yield* summary.diff({ @@ -511,7 +507,7 @@ export const SessionRoutes = lazy(() => description: "Successfully unshared session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -872,7 +868,7 @@ export const SessionRoutes = lazy(() => sessionID: SessionID.zod, }), ), - validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })), + validator("json", zodObject(SessionPrompt.PromptInput).omit({ sessionID: true })), async (c) => { c.status(200) c.header("Content-Type", "application/json") @@ -910,7 +906,7 @@ export const SessionRoutes = lazy(() => sessionID: SessionID.zod, }), ), - validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })), + validator("json", zodObject(SessionPrompt.PromptInput).omit({ sessionID: true })), async (c) => { const sessionID = c.req.valid("param").sessionID const body = c.req.valid("json") @@ -960,11 +956,11 @@ export const SessionRoutes = lazy(() => sessionID: SessionID.zod, }), ), - validator("json", SessionPrompt.CommandInput.omit({ sessionID: true })), + validator("json", zodObject(SessionPrompt.CommandInput).omit({ sessionID: true })), async (c) => jsonRequest("SessionRoutes.command", c, function* () { const sessionID = c.req.valid("param").sessionID - const body = c.req.valid("json") + const body = c.req.valid("json") as Omit const svc = yield* SessionPrompt.Service return yield* svc.command({ ...body, sessionID }) }), @@ -993,11 +989,11 @@ export const SessionRoutes = lazy(() => sessionID: SessionID.zod, }), ), - validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })), + validator("json", zodObject(SessionPrompt.ShellInput).omit({ sessionID: true })), async (c) => jsonRequest("SessionRoutes.shell", c, function* () { const sessionID = c.req.valid("param").sessionID - const body = c.req.valid("json") + const body = c.req.valid("json") as Omit const svc = yield* SessionPrompt.Service return yield* svc.shell({ ...body, sessionID }) }), @@ -1013,7 +1009,7 @@ export const SessionRoutes = lazy(() => description: "Updated session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, @@ -1026,16 +1022,14 @@ export const SessionRoutes = lazy(() => sessionID: SessionID.zod, }), ), - validator("json", SessionRevert.RevertInput.omit({ sessionID: true })), + validator("json", zodObject(SessionRevert.RevertInput).omit({ sessionID: true })), async (c) => { const sessionID = c.req.valid("param").sessionID - log.info("revert", c.req.valid("json")) + const body = c.req.valid("json") as Omit + log.info("revert", body) return jsonRequest("SessionRoutes.revert", c, function* () { const svc = yield* SessionRevert.Service - return yield* svc.revert({ - sessionID, - ...c.req.valid("json"), - }) + return yield* svc.revert({ sessionID, ...body }) }) }, ) @@ -1050,7 +1044,7 @@ export const SessionRoutes = lazy(() => description: "Updated session", content: { "application/json": { - schema: resolver(Session.Info), + schema: resolver(Session.Info.zod), }, }, }, diff --git a/packages/opencode/src/session/message.ts b/packages/opencode/src/session/message.ts index ced04b8e9..b1b245343 100644 --- a/packages/opencode/src/session/message.ts +++ b/packages/opencode/src/session/message.ts @@ -1,191 +1,192 @@ -import z from "zod" +import { Schema } from "effect" import { SessionID } from "./schema" import { ModelID, ProviderID } from "../provider/schema" -import { NamedError } from "@opencode-ai/shared/util/error" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" +import { namedSchemaError } from "@/util/named-schema-error" -export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({})) -export const AuthError = NamedError.create( - "ProviderAuthError", - z.object({ - providerID: z.string(), - message: z.string(), - }), -) - -export const ToolCall = z - .object({ - state: z.literal("call"), - step: z.number().optional(), - toolCallId: z.string(), - toolName: z.string(), - args: z.custom>(), - }) - .meta({ - ref: "ToolCall", - }) -export type ToolCall = z.infer - -export const ToolPartialCall = z - .object({ - state: z.literal("partial-call"), - step: z.number().optional(), - toolCallId: z.string(), - toolName: z.string(), - args: z.custom>(), - }) - .meta({ - ref: "ToolPartialCall", - }) -export type ToolPartialCall = z.infer - -export const ToolResult = z - .object({ - state: z.literal("result"), - step: z.number().optional(), - toolCallId: z.string(), - toolName: z.string(), - args: z.custom>(), - result: z.string(), - }) - .meta({ - ref: "ToolResult", - }) -export type ToolResult = z.infer - -export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).meta({ - ref: "ToolInvocation", +export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {}) +export const AuthError = namedSchemaError("ProviderAuthError", { + providerID: Schema.String, + message: Schema.String, }) -export type ToolInvocation = z.infer -export const TextPart = z - .object({ - type: z.literal("text"), - text: z.string(), - }) - .meta({ - ref: "TextPart", - }) -export type TextPart = z.infer +const AuthErrorEffect = Schema.Struct({ + name: Schema.Literal("ProviderAuthError"), + data: Schema.Struct({ + providerID: Schema.String, + message: Schema.String, + }), +}) -export const ReasoningPart = z - .object({ - type: z.literal("reasoning"), - text: z.string(), - providerMetadata: z.record(z.string(), z.any()).optional(), - }) - .meta({ - ref: "ReasoningPart", - }) -export type ReasoningPart = z.infer +const OutputLengthErrorEffect = Schema.Struct({ + name: Schema.Literal("MessageOutputLengthError"), + data: Schema.Struct({}), +}) -export const ToolInvocationPart = z - .object({ - type: z.literal("tool-invocation"), - toolInvocation: ToolInvocation, - }) - .meta({ - ref: "ToolInvocationPart", - }) -export type ToolInvocationPart = z.infer +const UnknownErrorEffect = Schema.Struct({ + name: Schema.Literal("UnknownError"), + data: Schema.Struct({ + message: Schema.String, + }), +}) -export const SourceUrlPart = z - .object({ - type: z.literal("source-url"), - sourceId: z.string(), - url: z.string(), - title: z.string().optional(), - providerMetadata: z.record(z.string(), z.any()).optional(), - }) - .meta({ - ref: "SourceUrlPart", - }) -export type SourceUrlPart = z.infer +export const ToolCall = Schema.Struct({ + state: Schema.Literal("call"), + step: Schema.optional(Schema.Number), + toolCallId: Schema.String, + toolName: Schema.String, + args: Schema.Unknown, +}) + .annotate({ identifier: "ToolCall" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolCall = Schema.Schema.Type -export const FilePart = z - .object({ - type: z.literal("file"), - mediaType: z.string(), - filename: z.string().optional(), - url: z.string(), - }) - .meta({ - ref: "FilePart", - }) -export type FilePart = z.infer +export const ToolPartialCall = Schema.Struct({ + state: Schema.Literal("partial-call"), + step: Schema.optional(Schema.Number), + toolCallId: Schema.String, + toolName: Schema.String, + args: Schema.Unknown, +}) + .annotate({ identifier: "ToolPartialCall" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolPartialCall = Schema.Schema.Type -export const StepStartPart = z - .object({ - type: z.literal("step-start"), - }) - .meta({ - ref: "StepStartPart", - }) -export type StepStartPart = z.infer +export const ToolResult = Schema.Struct({ + state: Schema.Literal("result"), + step: Schema.optional(Schema.Number), + toolCallId: Schema.String, + toolName: Schema.String, + args: Schema.Unknown, + result: Schema.String, +}) + .annotate({ identifier: "ToolResult" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolResult = Schema.Schema.Type -export const MessagePart = z - .discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart]) - .meta({ - ref: "MessagePart", - }) -export type MessagePart = z.infer +export const ToolInvocation = Schema.Union([ToolCall, ToolPartialCall, ToolResult]) + .annotate({ identifier: "ToolInvocation", discriminator: "state" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolInvocation = Schema.Schema.Type -export const Info = z - .object({ - id: z.string(), - role: z.enum(["user", "assistant"]), - parts: z.array(MessagePart), - metadata: z - .object({ - time: z.object({ - created: z.number(), - completed: z.number().optional(), +export const TextPart = Schema.Struct({ + type: Schema.Literal("text"), + text: Schema.String, +}) + .annotate({ identifier: "TextPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type TextPart = Schema.Schema.Type + +export const ReasoningPart = Schema.Struct({ + type: Schema.Literal("reasoning"), + text: Schema.String, + providerMetadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), +}) + .annotate({ identifier: "ReasoningPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ReasoningPart = Schema.Schema.Type + +export const ToolInvocationPart = Schema.Struct({ + type: Schema.Literal("tool-invocation"), + toolInvocation: ToolInvocation, +}) + .annotate({ identifier: "ToolInvocationPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolInvocationPart = Schema.Schema.Type + +export const SourceUrlPart = Schema.Struct({ + type: Schema.Literal("source-url"), + sourceId: Schema.String, + url: Schema.String, + title: Schema.optional(Schema.String), + providerMetadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), +}) + .annotate({ identifier: "SourceUrlPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type SourceUrlPart = Schema.Schema.Type + +export const FilePart = Schema.Struct({ + type: Schema.Literal("file"), + mediaType: Schema.String, + filename: Schema.optional(Schema.String), + url: Schema.String, +}) + .annotate({ identifier: "FilePart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type FilePart = Schema.Schema.Type + +export const StepStartPart = Schema.Struct({ + type: Schema.Literal("step-start"), +}) + .annotate({ identifier: "StepStartPart" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type StepStartPart = Schema.Schema.Type + +export const MessagePart = Schema.Union([ + TextPart, + ReasoningPart, + ToolInvocationPart, + SourceUrlPart, + FilePart, + StepStartPart, +]) + .annotate({ identifier: "MessagePart", discriminator: "type" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type MessagePart = Schema.Schema.Type + +export const Info = Schema.Struct({ + id: Schema.String, + role: Schema.Literals(["user", "assistant"]), + parts: Schema.Array(MessagePart), + metadata: Schema.Struct({ + time: Schema.Struct({ + created: Schema.Number, + completed: Schema.optional(Schema.Number), + }), + error: Schema.optional(Schema.Union([AuthErrorEffect, UnknownErrorEffect, OutputLengthErrorEffect])), + sessionID: SessionID, + tool: Schema.Record( + Schema.String, + Schema.StructWithRest( + Schema.Struct({ + title: Schema.String, + snapshot: Schema.optional(Schema.String), + time: Schema.Struct({ + start: Schema.Number, + end: Schema.Number, + }), }), - error: z - .discriminatedUnion("name", [AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema]) - .optional(), - sessionID: SessionID.zod, - tool: z.record( - z.string(), - z - .object({ - title: z.string(), - snapshot: z.string().optional(), - time: z.object({ - start: z.number(), - end: z.number(), - }), - }) - .catchall(z.any()), - ), - assistant: z - .object({ - system: z.string().array(), - modelID: ModelID.zod, - providerID: ProviderID.zod, - path: z.object({ - cwd: z.string(), - root: z.string(), - }), - cost: z.number(), - summary: z.boolean().optional(), - tokens: z.object({ - input: z.number(), - output: z.number(), - reasoning: z.number(), - cache: z.object({ - read: z.number(), - write: z.number(), - }), - }), - }) - .optional(), - snapshot: z.string().optional(), - }) - .meta({ ref: "MessageMetadata" }), - }) - .meta({ - ref: "Message", - }) -export type Info = z.infer + [Schema.Record(Schema.String, Schema.Unknown)], + ), + ), + assistant: Schema.optional( + Schema.Struct({ + system: Schema.Array(Schema.String), + modelID: ModelID, + providerID: ProviderID, + path: Schema.Struct({ + cwd: Schema.String, + root: Schema.String, + }), + cost: Schema.Number, + summary: Schema.optional(Schema.Boolean), + tokens: Schema.Struct({ + input: Schema.Number, + output: Schema.Number, + reasoning: Schema.Number, + cache: Schema.Struct({ + read: Schema.Number, + write: Schema.Number, + }), + }), + }), + ), + snapshot: Schema.optional(Schema.String), + }).annotate({ identifier: "MessageMetadata" }), +}) + .annotate({ identifier: "Message" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Schema.Schema.Type export * as Message from "./message" diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index fb8354dda..b55f9dcc7 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -62,7 +62,7 @@ export function toPartialRow(info: DeepPartial) { export default [ SyncEvent.project(Session.Event.Created, (db, data) => { - db.insert(SessionTable).values(Session.toRow(data.info)).run() + db.insert(SessionTable).values(Session.toRow(data.info as Session.Info)).run() }), SyncEvent.project(Session.Event.Updated, (db, data) => { diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 508c72cc8..0f48eb64e 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -43,7 +43,9 @@ import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Truncate } from "@/tool" import { decodeDataUrl } from "@/util/data-url" import { Process } from "@/util" -import { Cause, Effect, Exit, Layer, Option, Scope, Context } from "effect" +import { Cause, Effect, Exit, Layer, Option, Scope, Context, Schema } from "effect" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import { EffectLogger } from "@/effect" import { InstanceState } from "@/effect" import { TaskTool, type TaskPromptOps } from "@/tool/task" @@ -69,7 +71,7 @@ const elog = EffectLogger.create({ service: "session.prompt" }) export interface Interface { readonly cancel: (sessionID: SessionID) => Effect.Effect readonly prompt: (input: PromptInput) => Effect.Effect - readonly loop: (input: z.infer) => Effect.Effect + readonly loop: (input: LoopInput) => Effect.Effect readonly shell: (input: ShellInput) => Effect.Effect readonly command: (input: CommandInput) => Effect.Effect readonly resolvePromptParts: (template: string) => Effect.Effect @@ -1532,9 +1534,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the }, ) - const loop: (input: z.infer) => Effect.Effect = Effect.fn( - "SessionPrompt.loop", - )(function* (input: z.infer) { + const loop: (input: LoopInput) => Effect.Effect = Effect.fn("SessionPrompt.loop")(function* ( + input: LoopInput, + ) { return yield* state.ensureRunning(input.sessionID, lastAssistant(input.sessionID), runLoop(input.sessionID)) }) @@ -1701,91 +1703,88 @@ export const defaultLayer = Layer.suspend(() => ), ), ) -export const PromptInput = z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod.optional(), - model: z - .object({ - providerID: ProviderID.zod, - modelID: ModelID.zod, - }) - .optional(), - agent: z.string().optional(), - noReply: z.boolean().optional(), - tools: z - .record(z.string(), z.boolean()) - .optional() - .describe("@deprecated tools and permissions have been merged, you can set permissions on the session itself now"), - format: MessageV2.Format.zod.optional(), - system: z.string().optional(), - variant: z.string().optional(), - parts: z.array( - z.discriminatedUnion("type", [ - MessageV2.TextPartInput.zod as unknown as z.ZodObject, - MessageV2.FilePartInput.zod as unknown as z.ZodObject, - MessageV2.AgentPartInput.zod as unknown as z.ZodObject, - MessageV2.SubtaskPartInput.zod as unknown as z.ZodObject, - ]), - ), +const ModelRef = Schema.Struct({ + providerID: ProviderID, + modelID: ModelID, }) + +export const PromptInput = Schema.Struct({ + sessionID: SessionID, + messageID: Schema.optional(MessageID), + model: Schema.optional(ModelRef), + agent: Schema.optional(Schema.String), + noReply: Schema.optional(Schema.Boolean), + tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)).annotate({ + description: + "@deprecated tools and permissions have been merged, you can set permissions on the session itself now", + }), + format: Schema.optional(MessageV2.Format), + system: Schema.optional(Schema.String), + variant: Schema.optional(Schema.String), + parts: Schema.Array( + Schema.Union([ + MessageV2.TextPartInput, + MessageV2.FilePartInput, + MessageV2.AgentPartInput, + MessageV2.SubtaskPartInput, + ]).annotate({ discriminator: "type" }), + ), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) // `z.discriminatedUnion` erases the discriminated members' shapes back to -// `{}` because the derived `.zod` on each input is typed as an opaque -// `z.ZodType`. Restore the precise `parts` type from the exported Schema -// input types so callers see a proper tagged union. +// `{}` when walked from the generic `z.ZodType` input. Restore the precise +// `parts` type from the exported Schema input types so callers see a proper +// tagged union. type PartInputUnion = | MessageV2.TextPartInput | MessageV2.FilePartInput | MessageV2.AgentPartInput | MessageV2.SubtaskPartInput -export type PromptInput = Omit, "parts"> & { +export type PromptInput = Omit, "parts"> & { parts: PartInputUnion[] } -export const LoopInput = z.object({ - sessionID: SessionID.zod, -}) +export class LoopInput extends Schema.Class("SessionPrompt.LoopInput")({ + sessionID: SessionID, +}) { + static readonly zod = zod(this) +} -export const ShellInput = z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod.optional(), - agent: z.string(), - model: z - .object({ - providerID: ProviderID.zod, - modelID: ModelID.zod, - }) - .optional(), - command: z.string(), -}) -export type ShellInput = z.infer +export const ShellInput = Schema.Struct({ + sessionID: SessionID, + messageID: Schema.optional(MessageID), + agent: Schema.String, + model: Schema.optional(ModelRef), + command: Schema.String, +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ShellInput = Schema.Schema.Type -export const CommandInput = z.object({ - messageID: MessageID.zod.optional(), - sessionID: SessionID.zod, - agent: z.string().optional(), - model: z.string().optional(), - arguments: z.string(), - command: z.string(), - variant: z.string().optional(), - // Inlined (no `.meta({ ref })`) to keep the original SDK output — the +export const CommandInput = Schema.Struct({ + messageID: Schema.optional(MessageID), + sessionID: SessionID, + agent: Schema.optional(Schema.String), + model: Schema.optional(Schema.String), + arguments: Schema.String, + command: Schema.String, + variant: Schema.optional(Schema.String), + // Inlined (no identifier annotation) to keep the original SDK output — the // PromptInput call site below references FilePartInput by ref via the // Schema export in message-v2.ts. - parts: z - .array( - z.discriminatedUnion("type", [ - z.object({ - id: PartID.zod.optional(), - type: z.literal("file"), - mime: z.string(), - filename: z.string().optional(), - url: z.string(), - source: MessageV2.FilePartSource.zod.optional(), + parts: Schema.optional( + Schema.Array( + Schema.Union([ + Schema.Struct({ + id: Schema.optional(PartID), + type: Schema.Literal("file"), + mime: Schema.String, + filename: Schema.optional(Schema.String), + url: Schema.String, + source: Schema.optional(MessageV2.FilePartSource), }), - ]), - ) - .optional(), -}) -export type CommandInput = z.infer + ]).annotate({ discriminator: "type" }), + ), + ), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type CommandInput = Schema.Schema.Type /** @internal Exported for testing */ export function createStructuredOutputTool(input: { diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index c7e5220f1..e1db26510 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -1,10 +1,11 @@ -import z from "zod" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema } from "effect" import { Bus } from "../bus" import { Snapshot } from "../snapshot" import { Storage } from "@/storage" import { SyncEvent } from "../sync" import { Log } from "../util" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import * as Session from "./session" import { MessageV2 } from "./message-v2" import { SessionID, MessageID, PartID } from "./schema" @@ -13,12 +14,12 @@ import { SessionSummary } from "./summary" const log = Log.create({ service: "session.revert" }) -export const RevertInput = z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod, - partID: PartID.zod.optional(), -}) -export type RevertInput = z.infer +export const RevertInput = Schema.Struct({ + sessionID: SessionID, + messageID: MessageID, + partID: Schema.optional(PartID), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type RevertInput = Schema.Schema.Type export interface Interface { readonly revert: (input: RevertInput) => Effect.Effect diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index a7607798b..d2bdbccb7 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -27,7 +27,9 @@ import { SessionID, MessageID, PartID } from "./schema" import type { Provider } from "@/provider" import { Permission } from "@/permission" import { Global } from "@/global" -import { Effect, Layer, Option, Context } from "effect" +import { Effect, Layer, Option, Context, Schema, Types } from "effect" +import { zod, zodObject } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" const log = Log.create({ service: "session" }) @@ -114,91 +116,104 @@ function getForkedTitle(title: string): string { return `${title} (fork #1)` } -export const Info = z - .object({ - id: SessionID.zod, - slug: z.string(), - projectID: ProjectID.zod, - workspaceID: WorkspaceID.zod.optional(), - directory: z.string(), - parentID: SessionID.zod.optional(), - summary: z - .object({ - additions: z.number(), - deletions: z.number(), - files: z.number(), - diffs: Snapshot.FileDiff.zod.array().optional(), - }) - .optional(), - share: z - .object({ - url: z.string(), - }) - .optional(), - title: z.string(), - version: z.string(), - time: z.object({ - created: z.number(), - updated: z.number(), - compacting: z.number().optional(), - archived: z.number().optional(), - }), - permission: Permission.Ruleset.zod.optional(), - revert: z - .object({ - messageID: MessageID.zod, - partID: PartID.zod.optional(), - snapshot: z.string().optional(), - diff: z.string().optional(), - }) - .optional(), - }) - .meta({ - ref: "Session", - }) -export type Info = z.output - -export const ProjectInfo = z - .object({ - id: ProjectID.zod, - name: z.string().optional(), - worktree: z.string(), - }) - .meta({ - ref: "ProjectSummary", - }) -export type ProjectInfo = z.output - -export const GlobalInfo = Info.extend({ - project: ProjectInfo.nullable(), -}).meta({ - ref: "GlobalSession", +const Summary = Schema.Struct({ + additions: Schema.Number, + deletions: Schema.Number, + files: Schema.Number, + diffs: Schema.optional(Schema.Array(Snapshot.FileDiff)), }) -export type GlobalInfo = z.output -export const CreateInput = z - .object({ - parentID: SessionID.zod.optional(), - title: z.string().optional(), - permission: Info.shape.permission, - workspaceID: WorkspaceID.zod.optional(), - }) - .optional() -export type CreateInput = z.output - -export const ForkInput = z.object({ sessionID: SessionID.zod, messageID: MessageID.zod.optional() }) -export const GetInput = SessionID.zod -export const ChildrenInput = SessionID.zod -export const RemoveInput = SessionID.zod -export const SetTitleInput = z.object({ sessionID: SessionID.zod, title: z.string() }) -export const SetArchivedInput = z.object({ sessionID: SessionID.zod, time: z.number().optional() }) -export const SetPermissionInput = z.object({ sessionID: SessionID.zod, permission: Permission.Ruleset.zod }) -export const SetRevertInput = z.object({ - sessionID: SessionID.zod, - revert: Info.shape.revert, - summary: Info.shape.summary, +const Share = Schema.Struct({ + url: Schema.String, }) -export const MessagesInput = z.object({ sessionID: SessionID.zod, limit: z.number().optional() }) + +const Time = Schema.Struct({ + created: Schema.Number, + updated: Schema.Number, + compacting: Schema.optional(Schema.Number), + archived: Schema.optional(Schema.Number), +}) + +const Revert = Schema.Struct({ + messageID: MessageID, + partID: Schema.optional(PartID), + snapshot: Schema.optional(Schema.String), + diff: Schema.optional(Schema.String), +}) + +export const Info = Schema.Struct({ + id: SessionID, + slug: Schema.String, + projectID: ProjectID, + workspaceID: Schema.optional(WorkspaceID), + directory: Schema.String, + parentID: Schema.optional(SessionID), + summary: Schema.optional(Summary), + share: Schema.optional(Share), + title: Schema.String, + version: Schema.String, + time: Time, + permission: Schema.optional(Permission.Ruleset), + revert: Schema.optional(Revert), +}) + .annotate({ identifier: "Session" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Types.DeepMutable> + +export const ProjectInfo = Schema.Struct({ + id: ProjectID, + name: Schema.optional(Schema.String), + worktree: Schema.String, +}) + .annotate({ identifier: "ProjectSummary" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ProjectInfo = Types.DeepMutable> + +export const GlobalInfo = Schema.Struct({ + ...Info.fields, + project: Schema.NullOr(ProjectInfo), +}) + .annotate({ identifier: "GlobalSession" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type GlobalInfo = Types.DeepMutable> + +export const CreateInput = Schema.optional( + Schema.Struct({ + parentID: Schema.optional(SessionID), + title: Schema.optional(Schema.String), + permission: Schema.optional(Permission.Ruleset), + workspaceID: Schema.optional(WorkspaceID), + }), +).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type CreateInput = Types.DeepMutable> + +export const ForkInput = Schema.Struct({ + sessionID: SessionID, + messageID: Schema.optional(MessageID), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const GetInput = SessionID +export const ChildrenInput = SessionID +export const RemoveInput = SessionID +export const SetTitleInput = Schema.Struct({ sessionID: SessionID, title: Schema.String }).pipe( + withStatics((s) => ({ zod: zod(s) })), +) +export const SetArchivedInput = Schema.Struct({ + sessionID: SessionID, + time: Schema.optional(Schema.Number), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const SetPermissionInput = Schema.Struct({ + sessionID: SessionID, + permission: Permission.Ruleset, +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const SetRevertInput = Schema.Struct({ + sessionID: SessionID, + revert: Schema.optional(Revert), + summary: Schema.optional(Summary), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export const MessagesInput = Schema.Struct({ + sessionID: SessionID, + limit: Schema.optional(Schema.Number), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) export const Event = { Created: SyncEvent.define({ @@ -207,7 +222,7 @@ export const Event = { aggregate: "sessionID", schema: z.object({ sessionID: SessionID.zod, - info: Info, + info: Info.zod, }), }), Updated: SyncEvent.define({ @@ -216,14 +231,14 @@ export const Event = { aggregate: "sessionID", schema: z.object({ sessionID: SessionID.zod, - info: updateSchema(Info).extend({ - share: updateSchema(Info.shape.share.unwrap()).optional(), - time: updateSchema(Info.shape.time).optional(), + info: updateSchema(zodObject(Info)).extend({ + share: updateSchema(zodObject(Share)).optional(), + time: updateSchema(zodObject(Time)).optional(), }), }), busSchema: z.object({ sessionID: SessionID.zod, - info: Info, + info: Info.zod, }), }), Deleted: SyncEvent.define({ @@ -232,7 +247,7 @@ export const Event = { aggregate: "sessionID", schema: z.object({ sessionID: SessionID.zod, - info: Info, + info: Info.zod, }), }), Diff: BusEvent.define( diff --git a/packages/opencode/src/session/status.ts b/packages/opencode/src/session/status.ts index 7f46c70a8..b9b9fd7e7 100644 --- a/packages/opencode/src/session/status.ts +++ b/packages/opencode/src/session/status.ts @@ -2,35 +2,35 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { InstanceState } from "@/effect" import { SessionID } from "./schema" -import { Effect, Layer, Context } from "effect" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" +import { Effect, Layer, Context, Schema } from "effect" import z from "zod" -export const Info = z - .union([ - z.object({ - type: z.literal("idle"), - }), - z.object({ - type: z.literal("retry"), - attempt: z.number(), - message: z.string(), - next: z.number(), - }), - z.object({ - type: z.literal("busy"), - }), - ]) - .meta({ - ref: "SessionStatus", - }) -export type Info = z.infer +export const Info = Schema.Union([ + Schema.Struct({ + type: Schema.Literal("idle"), + }), + Schema.Struct({ + type: Schema.Literal("retry"), + attempt: Schema.Number, + message: Schema.String, + next: Schema.Number, + }), + Schema.Struct({ + type: Schema.Literal("busy"), + }), +]) + .annotate({ identifier: "SessionStatus" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Schema.Schema.Type export const Event = { Status: BusEvent.define( "session.status", z.object({ sessionID: SessionID.zod, - status: Info, + status: Info.zod, }), ), // deprecated diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 70b3102f6..04a24d2c2 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -1,8 +1,9 @@ -import z from "zod" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema } from "effect" import { Bus } from "@/bus" import { Snapshot } from "@/snapshot" import { Storage } from "@/storage" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import * as Session from "./session" import { MessageV2 } from "./message-v2" import { SessionID, MessageID } from "./schema" @@ -155,9 +156,10 @@ export const defaultLayer = Layer.suspend(() => ), ) -export const DiffInput = z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod.optional(), -}) +export const DiffInput = Schema.Struct({ + sessionID: SessionID, + messageID: Schema.optional(MessageID), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type DiffInput = Schema.Schema.Type export * as SessionSummary from "./summary" diff --git a/packages/opencode/src/session/todo.ts b/packages/opencode/src/session/todo.ts index 4840f86a3..257b586ed 100644 --- a/packages/opencode/src/session/todo.ts +++ b/packages/opencode/src/session/todo.ts @@ -1,26 +1,30 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { SessionID } from "./schema" -import { Effect, Layer, Context } from "effect" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" +import { Effect, Layer, Context, Schema } from "effect" import z from "zod" import { Database, eq, asc } from "../storage" import { TodoTable } from "./session.sql" -export const Info = z - .object({ - content: z.string().describe("Brief description of the task"), - status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"), - priority: z.string().describe("Priority level of the task: high, medium, low"), - }) - .meta({ ref: "Todo" }) -export type Info = z.infer +export const Info = Schema.Struct({ + content: Schema.String.annotate({ description: "Brief description of the task" }), + status: Schema.String.annotate({ + description: "Current status of the task: pending, in_progress, completed, cancelled", + }), + priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }), +}) + .annotate({ identifier: "Todo" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Schema.Schema.Type export const Event = { Updated: BusEvent.define( "todo.updated", z.object({ sessionID: SessionID.zod, - todos: z.array(Info), + todos: z.array(Info.zod), }), ), } diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts index 5090f17a7..c08fb0411 100644 --- a/packages/opencode/src/tool/todo.ts +++ b/packages/opencode/src/tool/todo.ts @@ -4,8 +4,21 @@ import * as Tool from "./tool" import DESCRIPTION_WRITE from "./todowrite.txt" import { Todo } from "../session/todo" +// Parameters are kept inline rather than derived from Todo.Info because +// Tool.define requires z.ZodObject-typed parameters for execute() inference, +// and zodObject(Todo.Info) returns ZodObject — reaching into .shape would +// erase field types. Tool schemas migrate to Effect Schema as a separate slice +// per specs/effect/schema.md. const parameters = z.object({ - todos: z.array(z.object(Todo.Info.shape)).describe("The updated todo list"), + todos: z + .array( + z.object({ + content: z.string().describe("Brief description of the task"), + status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"), + priority: z.string().describe("Priority level of the task: high, medium, low"), + }), + ) + .describe("The updated todo list"), }) type Metadata = { diff --git a/packages/opencode/src/util/effect-zod.ts b/packages/opencode/src/util/effect-zod.ts index f6d2c5e5c..edbbf4d54 100644 --- a/packages/opencode/src/util/effect-zod.ts +++ b/packages/opencode/src/util/effect-zod.ts @@ -22,6 +22,33 @@ export function zod(schema: S): z.ZodType> } +/** + * Derive a Zod value from an Effect Schema (or a Schema-backed export with a + * `.zod` static) and narrow the result to `z.ZodObject` so `.shape`, + * `.omit`, `.extend`, and friends are accessible. + * + * The `zod()` walker returns `z.ZodType` because not every AST node decodes + * to an object; this helper keeps the "I started from a `Schema.Struct`" cast + * in one place instead of sprinkling `as unknown as z.ZodObject` across + * call sites. + * + * The return is intentionally loose — carrying Schema field types through the + * mapped `.omit()` / `.extend()` surface triggers brand-intersection + * explosions for branded primitives (`string & Brand<"SessionID">` extends + * `object` via the brand and gets walked into the prototype by `DeepPartial`, + * `updateSchema`, etc.), and zod's inference through `z.ZodType` + * wrappers also can't reconstruct `T` cleanly. Consumers that care about the + * post-`.omit()` shape should cast `c.req.valid(...)` to the expected type. + */ +export function zodObject(schema: S): z.ZodObject { + const derived: z.ZodTypeAny = "zod" in schema && isZodType(schema.zod) ? schema.zod : walk(schema.ast) + return derived as unknown as z.ZodObject +} + +function isZodType(value: unknown): value is z.ZodTypeAny { + return typeof value === "object" && value !== null && "_zod" in value +} + function walk(ast: SchemaAST.AST): z.ZodTypeAny { const cached = walkCache.get(ast) if (cached) return cached diff --git a/packages/opencode/test/server/global-session-list.test.ts b/packages/opencode/test/server/global-session-list.test.ts index d0f71b8fd..03b1a0346 100644 --- a/packages/opencode/test/server/global-session-list.test.ts +++ b/packages/opencode/test/server/global-session-list.test.ts @@ -18,7 +18,7 @@ const svc = { create(input?: SessionNs.CreateInput) { return run(SessionNs.Service.use((svc) => svc.create(input))) }, - setArchived(input: z.output) { + setArchived(input: z.output) { return run(SessionNs.Service.use((svc) => svc.setArchived(input))) }, } diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 037613d46..4fe9c1551 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -38,7 +38,7 @@ const svc = { create(input?: SessionNs.CreateInput) { return run(SessionNs.Service.use((svc) => svc.create(input))) }, - messages(input: z.output) { + messages(input: z.output) { return run(SessionNs.Service.use((svc) => svc.messages(input))) }, updateMessage(msg: T) { diff --git a/packages/opencode/test/session/schema-decoding.test.ts b/packages/opencode/test/session/schema-decoding.test.ts new file mode 100644 index 000000000..5894b2615 --- /dev/null +++ b/packages/opencode/test/session/schema-decoding.test.ts @@ -0,0 +1,310 @@ +import { describe, expect, test } from "bun:test" +import { Schema } from "effect" + +import { Session } from "../../src/session" +import { SessionPrompt } from "../../src/session/prompt" +import { SessionRevert } from "../../src/session/revert" +import { SessionStatus } from "../../src/session/status" +import { SessionSummary } from "../../src/session/summary" +import { Todo } from "../../src/session/todo" +import { SessionID, MessageID, PartID } from "../../src/session/schema" +import { ProjectID } from "../../src/project/schema" +import { WorkspaceID } from "../../src/control-plane/schema" + +// Covers the session-domain Effect Schema migration. For each migrated +// schema we assert: +// 1. The Effect decoder (`Schema.decodeUnknownSync`) accepts valid input. +// 2. The derived Zod (`X.zod.parse`) accepts the same input and returns the +// same shape. +// 3. Clearly-invalid input is rejected by both paths. +// +// The point is to lock down the Schema <-> Zod bridge so a future edit to +// any input schema can't silently drop or widen a field on one side. + +// Representative valid IDs — the branded schemas require the right prefix +// (see src/id/id.ts). +const sessionID = SessionID.zod.parse("ses_01J5Y5H0AH4Q4NXJ6P4C3P5V2K") +const sessionIDChild = SessionID.zod.parse("ses_01J5Y5H0AH4Q4NXJ6P4C3P5V2L") +const messageID = MessageID.zod.parse("msg_01J5Y5H0AH4Q4NXJ6P4C3P5V2M") +const partID = PartID.zod.parse("prt_01J5Y5H0AH4Q4NXJ6P4C3P5V2N") +const projectID = ProjectID.zod.parse("proj-alpha") +const workspaceID = WorkspaceID.zod.parse("wrk-primary") + +function decodeUnknown(schema: S) { + const decode = Schema.decodeUnknownSync(schema as any) + return (input: unknown): Schema.Schema.Type => decode(input) as Schema.Schema.Type +} + +describe("Session.Info", () => { + const decode = decodeUnknown(Session.Info) + + test("accepts minimal session", () => { + const input = { + id: sessionID, + slug: "hello", + projectID, + directory: "/tmp/proj", + title: "First session", + version: "0.1.0", + time: { created: 1, updated: 2 }, + } + expect(decode(input)).toEqual(input) + expect(Session.Info.zod.parse(input)).toEqual(input) + }) + + test("round-trips every optional field", () => { + const input = { + id: sessionID, + slug: "fullshape", + projectID, + workspaceID, + directory: "/tmp/proj", + parentID: sessionIDChild, + summary: { + additions: 10, + deletions: 5, + files: 2, + diffs: [{ additions: 1, deletions: 0, file: "a.ts", patch: "--- a/a.ts" }], + }, + share: { url: "https://share.example.com/s/1" }, + title: "Full session", + version: "1.0.0", + time: { created: 100, updated: 200, compacting: 150, archived: 300 }, + permission: [{ action: "allow" as const, pattern: "*", permission: "read" }], + revert: { + messageID, + partID, + snapshot: "snap-1", + diff: "diff-1", + }, + } + expect(decode(input)).toEqual(input) + expect(Session.Info.zod.parse(input)).toEqual(input) + }) + + test("rejects unbranded session id", () => { + const bad = { id: "not-a-session-id" } as unknown + expect(() => decode(bad)).toThrow() + expect(() => Session.Info.zod.parse(bad)).toThrow() + }) + + test("rejects missing required fields", () => { + const bad = { id: sessionID } as unknown + expect(() => decode(bad)).toThrow() + expect(() => Session.Info.zod.parse(bad)).toThrow() + }) +}) + +describe("Session.ProjectInfo", () => { + const decode = decodeUnknown(Session.ProjectInfo) + + test("accepts with and without optional name", () => { + const noName = { id: projectID, worktree: "/tmp/wt" } + const withName = { ...noName, name: "alpha" } + expect(decode(noName)).toEqual(noName) + expect(decode(withName)).toEqual(withName) + expect(Session.ProjectInfo.zod.parse(noName)).toEqual(noName) + expect(Session.ProjectInfo.zod.parse(withName)).toEqual(withName) + }) +}) + +describe("Session.GlobalInfo", () => { + const decode = decodeUnknown(Session.GlobalInfo) + + test("accepts null project", () => { + const input = { + id: sessionID, + slug: "global", + projectID, + directory: "/tmp/proj", + title: "global", + version: "0", + time: { created: 0, updated: 0 }, + project: null, + } + expect(decode(input)).toEqual(input) + expect(Session.GlobalInfo.zod.parse(input)).toEqual(input) + }) + + test("accepts populated project", () => { + const input = { + id: sessionID, + slug: "global", + projectID, + directory: "/tmp/proj", + title: "global", + version: "0", + time: { created: 0, updated: 0 }, + project: { id: projectID, worktree: "/tmp/wt", name: "alpha" }, + } + expect(decode(input)).toEqual(input) + expect(Session.GlobalInfo.zod.parse(input)).toEqual(input) + }) +}) + +describe("Session input schemas", () => { + test("CreateInput accepts undefined and populated forms", () => { + const decode = decodeUnknown(Session.CreateInput) + expect(decode(undefined)).toBeUndefined() + expect(Session.CreateInput.zod.parse(undefined)).toBeUndefined() + + const populated = { + parentID: sessionID, + title: "child", + permission: [{ action: "ask" as const, pattern: "*", permission: "bash" }], + workspaceID, + } + expect(decode(populated)).toEqual(populated) + expect(Session.CreateInput.zod.parse(populated)).toEqual(populated) + }) + + test("ForkInput round-trips", () => { + const decode = decodeUnknown(Session.ForkInput) + const input = { sessionID, messageID } + expect(decode(input)).toEqual(input) + expect(Session.ForkInput.zod.parse(input)).toEqual(input) + // messageID is optional + const bare = { sessionID } + expect(decode(bare)).toEqual(bare) + expect(Session.ForkInput.zod.parse(bare)).toEqual(bare) + }) + + test("SetTitleInput rejects missing title", () => { + expect(() => decodeUnknown(Session.SetTitleInput)({ sessionID })).toThrow() + expect(() => Session.SetTitleInput.zod.parse({ sessionID })).toThrow() + }) + + test("SetArchivedInput accepts both with and without time", () => { + const decode = decodeUnknown(Session.SetArchivedInput) + expect(decode({ sessionID })).toEqual({ sessionID }) + expect(decode({ sessionID, time: 123 })).toEqual({ sessionID, time: 123 }) + }) + + test("SetPermissionInput requires a ruleset", () => { + const decode = decodeUnknown(Session.SetPermissionInput) + const input = { sessionID, permission: [{ action: "deny" as const, pattern: "*", permission: "write" }] } + expect(decode(input)).toEqual(input) + expect(() => decode({ sessionID })).toThrow() + }) + + test("MessagesInput accepts optional limit", () => { + const decode = decodeUnknown(Session.MessagesInput) + expect(decode({ sessionID })).toEqual({ sessionID }) + expect(decode({ sessionID, limit: 50 })).toEqual({ sessionID, limit: 50 }) + }) +}) + +describe("SessionRevert.RevertInput", () => { + const decode = decodeUnknown(SessionRevert.RevertInput) + + test("messageID is required, partID is optional", () => { + const withPart = { sessionID, messageID, partID } + expect(decode(withPart)).toEqual(withPart) + expect(SessionRevert.RevertInput.zod.parse(withPart)).toEqual(withPart) + + const noPart = { sessionID, messageID } + expect(decode(noPart)).toEqual(noPart) + expect(SessionRevert.RevertInput.zod.parse(noPart)).toEqual(noPart) + + expect(() => decode({ sessionID })).toThrow() + expect(() => SessionRevert.RevertInput.zod.parse({ sessionID })).toThrow() + }) +}) + +describe("SessionSummary.DiffInput", () => { + const decode = decodeUnknown(SessionSummary.DiffInput) + + test("messageID optional", () => { + expect(decode({ sessionID })).toEqual({ sessionID }) + expect(decode({ sessionID, messageID })).toEqual({ sessionID, messageID }) + }) +}) + +describe("SessionStatus.Info", () => { + const decode = decodeUnknown(SessionStatus.Info) + + test("idle / busy discriminators", () => { + expect(decode({ type: "idle" })).toEqual({ type: "idle" }) + expect(decode({ type: "busy" })).toEqual({ type: "busy" }) + expect(SessionStatus.Info.zod.parse({ type: "idle" })).toEqual({ type: "idle" }) + }) + + test("retry carries attempt/message/next", () => { + const input = { type: "retry" as const, attempt: 1, message: "transient", next: 500 } + expect(decode(input)).toEqual(input) + expect(SessionStatus.Info.zod.parse(input)).toEqual(input) + }) + + test("rejects unknown type", () => { + expect(() => decode({ type: "bogus" })).toThrow() + expect(() => SessionStatus.Info.zod.parse({ type: "bogus" })).toThrow() + }) +}) + +describe("Todo.Info", () => { + const decode = decodeUnknown(Todo.Info) + + test("three-field round-trip", () => { + const input = { content: "do a thing", status: "pending", priority: "high" } + expect(decode(input)).toEqual(input) + expect(Todo.Info.zod.parse(input)).toEqual(input) + }) +}) + +describe("SessionPrompt input schemas", () => { + test("LoopInput is just sessionID", () => { + const decode = decodeUnknown(SessionPrompt.LoopInput) + expect(decode({ sessionID })).toEqual({ sessionID }) + expect(SessionPrompt.LoopInput.zod.parse({ sessionID } as unknown)).toEqual({ sessionID }) + }) + + test("ShellInput requires agent + command", () => { + const decode = decodeUnknown(SessionPrompt.ShellInput) + const expected = { sessionID, agent: "build", command: "echo hi" } + const input: unknown = expected + expect(decode(input)).toEqual(expected) + expect(SessionPrompt.ShellInput.zod.parse(input as unknown)).toEqual(expected) + expect(() => decode({ sessionID })).toThrow() + }) + + test("PromptInput accepts a text part and a file part", () => { + const decode = decodeUnknown(SessionPrompt.PromptInput) + const expected = { + sessionID, + parts: [ + { type: "text" as const, text: "hello" }, + { type: "file" as const, mime: "image/png", url: "data:image/png;base64,AAAA" }, + ], + } + const input: unknown = expected + const decoded = decode(input) + expect(decoded.parts).toHaveLength(2) + expect(decoded.parts[0]).toMatchObject({ type: "text", text: "hello" }) + expect(decoded.parts[1]).toMatchObject({ type: "file", mime: "image/png" }) + + const viaZod = SessionPrompt.PromptInput.zod.parse(input) + expect(viaZod.parts).toHaveLength(2) + }) + + test("PromptInput rejects unknown part type", () => { + const decode = decodeUnknown(SessionPrompt.PromptInput) + const bad = { + sessionID, + parts: [{ type: "nonsense", payload: 42 }], + } + expect(() => decode(bad)).toThrow() + expect(() => SessionPrompt.PromptInput.zod.parse(bad)).toThrow() + }) + + test("CommandInput round-trips core fields", () => { + const decode = decodeUnknown(SessionPrompt.CommandInput) + const expected = { + sessionID, + arguments: "--flag", + command: "deploy", + } + const input: unknown = expected + expect(decode(input)).toEqual(expected) + expect(SessionPrompt.CommandInput.zod.parse(input)).toEqual(expected) + }) +}) From 2cd89d64e9c392a59cda5871d5b2c1ef0929ff7e Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 15:31:16 +0000 Subject: [PATCH 080/426] chore: generate --- packages/opencode/src/session/projectors.ts | 4 +- packages/sdk/openapi.json | 50 ++++++++++----------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index b55f9dcc7..7b3b0a7f2 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -62,7 +62,9 @@ export function toPartialRow(info: DeepPartial) { export default [ SyncEvent.project(Session.Event.Created, (db, data) => { - db.insert(SessionTable).values(Session.toRow(data.info as Session.Info)).run() + db.insert(SessionTable) + .values(Session.toRow(data.info as Session.Info)) + .run() }), SyncEvent.project(Session.Event.Updated, (db, data) => { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index d9954d915..55570d47d 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -4174,34 +4174,30 @@ "parts": { "type": "array", "items": { - "anyOf": [ - { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^prt.*" - }, - "type": { - "type": "string", - "const": "file" - }, - "mime": { - "type": "string" - }, - "filename": { - "type": "string" - }, - "url": { - "type": "string" - }, - "source": { - "$ref": "#/components/schemas/FilePartSource" - } - }, - "required": ["type", "mime", "url"] + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^prt.*" + }, + "type": { + "type": "string", + "const": "file" + }, + "mime": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "url": { + "type": "string" + }, + "source": { + "$ref": "#/components/schemas/FilePartSource" } - ] + }, + "required": ["type", "mime", "url"] } } }, From eb7555d3c62a3b3cb61fc87bc124ee7309e9aaab Mon Sep 17 00:00:00 2001 From: opencode Date: Thu, 23 Apr 2026 15:56:49 +0000 Subject: [PATCH 081/426] sync release versions for v1.14.22 --- bun.lock | 32 +++++++++++++------------- 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-electron/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/shared/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index addecfff2..06a5cfdb4 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -83,7 +83,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -117,7 +117,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -144,7 +144,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -168,7 +168,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -192,7 +192,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -225,7 +225,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -269,7 +269,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -298,7 +298,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -314,7 +314,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.14.21", + "version": "1.14.22", "bin": { "opencode": "./bin/opencode", }, @@ -459,7 +459,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -494,7 +494,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "cross-spawn": "catalog:", }, @@ -509,7 +509,7 @@ }, "packages/shared": { "name": "@opencode-ai/shared", - "version": "1.14.21", + "version": "1.14.22", "bin": { "opencode": "./bin/opencode", }, @@ -533,7 +533,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -568,7 +568,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -617,7 +617,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.14.21", + "version": "1.14.22", "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 7572f0b22..0ab5e304a 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.14.21", + "version": "1.14.22", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 1182f1794..3d3752750 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index f79ef3ecb..70d44b382 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.14.21", + "version": "1.14.22", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index c3c549004..55791a307 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.14.21", + "version": "1.14.22", "$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 f4c59c196..4966bf3e6 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.14.21", + "version": "1.14.22", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 3f5ca841b..a528a9a3a 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 0e349f81c..32b63fff1 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 77b0ec200..e4428f0cb 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.14.21", + "version": "1.14.22", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 5a9d2dc1a..6449b8df6 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.14.21" +version = "1.14.22" 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.14.21/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/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.14.21/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/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.14.21/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 925e458cf..5b06d0368 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.14.21", + "version": "1.14.22", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index cd9c0f0a8..ea8724ceb 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.21", + "version": "1.14.22", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index d71901066..ee8210ec1 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.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 3d07efef4..62a50b743 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.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/shared/package.json b/packages/shared/package.json index 117c12b98..48ae0fb78 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.21", + "version": "1.14.22", "name": "@opencode-ai/shared", "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index 2eb380dc7..a3516932a 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index a15918dc8..ba0fb0149 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.14.21", + "version": "1.14.22", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 9924f2e51..818cdf721 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.14.21", + "version": "1.14.22", "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 a43348670..e238b3670 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.14.21", + "version": "1.14.22", "publisher": "sst-dev", "repository": { "type": "git", From 9df7c78ebe051a3e71ec6aa27e38a4baa0bbb4bc Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Thu, 23 Apr 2026 21:28:54 +0530 Subject: [PATCH 082/426] fix(npm): respect npmrc for version lookups (#24016) --- packages/opencode/src/installation/index.ts | 19 +++-- packages/opencode/src/npm/index.ts | 56 ++++++++----- .../test/installation/installation.test.ts | 46 ++++++++-- packages/opencode/test/npm.test.ts | 84 +++++++++++++++++++ 4 files changed, 171 insertions(+), 34 deletions(-) diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index babde9dc4..e39b14b8f 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -132,6 +132,15 @@ export const layer: Layer.Layer Effect.succeed({ code: ChildProcessSpawner.ExitCode(1), stdout: "", stderr: "" })), ) + const viewVersion = Effect.fnUntraced(function* (method: "npm" | "pnpm" | "bun", spec: string) { + const args = method === "bun" ? ["pm", "view", spec, "version", "--json"] : ["view", spec, "version", "--json"] + const result = yield* run([method, ...args]) + if (result.code !== 0 || !result.stdout.trim()) { + return yield* new UpgradeFailedError({ stderr: result.stderr || result.stdout || `Failed to resolve ${spec}` }) + } + return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.String))(result.stdout) + }) + const getBrewFormula = Effect.fnUntraced(function* () { const tapFormula = yield* text(["brew", "list", "--formula", "anomalyco/tap/opencode"]) if (tapFormula.includes("opencode")) return "anomalyco/tap/opencode" @@ -217,15 +226,7 @@ export const layer: Layer.Layer()("NpmInstallFailedError", { @@ -106,7 +108,36 @@ export const layer = Layer.effect( const global = yield* Global.Service const fs = yield* FileSystem.FileSystem const flock = yield* EffectFlock.Service + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner const directory = (pkg: string) => path.join(global.cache, "packages", sanitize(pkg)) + const runView = Effect.fnUntraced( + function* (cmd: string[]) { + const handle = yield* spawner.spawn( + ChildProcess.make(cmd[0], cmd.slice(1), { + extendEnv: true, + }), + ) + const [stdout, stderr] = yield* Effect.all( + [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], + { concurrency: 2 }, + ) + const code = yield* handle.exitCode + if (code !== 0 || !stdout.trim()) { + return yield* Effect.fail(stderr || stdout || `Failed to run ${cmd.join(" ")}`) + } + return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.String))(stdout) + }, + Effect.scoped, + ) + const viewLatestVersion = Effect.fnUntraced(function* (pkg: string) { + return yield* runView(["npm", "view", pkg, "dist-tags.latest", "--json"]).pipe( + Effect.catch(() => + runView(["pnpm", "view", pkg, "dist-tags.latest", "--json"]).pipe( + Effect.catch(() => runView(["bun", "pm", "view", pkg, "dist-tags.latest", "--json"])), + ), + ), + ) + }) const reify = (input: { dir: string; add?: string[] }) => Effect.gen(function* () { yield* flock.acquire(`npm-install:${input.dir}`) @@ -143,29 +174,15 @@ export const layer = Layer.effect( ) const outdated = Effect.fn("Npm.outdated")(function* (pkg: string, cachedVersion: string) { - const response = yield* Effect.tryPromise({ - try: () => fetch(`https://registry.npmjs.org/${pkg}`), - catch: () => undefined, - }).pipe(Effect.orElseSucceed(() => undefined)) - - if (!response || !response.ok) { - return false - } - - const data = yield* Effect.tryPromise({ - try: () => response.json() as Promise<{ "dist-tags"?: { latest?: string } }>, - catch: () => undefined, - }).pipe(Effect.orElseSucceed(() => undefined)) - - const latestVersion = data?.["dist-tags"]?.latest - if (!latestVersion) { + const latestVersion = yield* viewLatestVersion(pkg).pipe(Effect.option) + if (Option.isNone(latestVersion)) { return false } const range = /[\s^~*xX<>|=]/.test(cachedVersion) - if (range) return !semver.satisfies(latestVersion, cachedVersion) + if (range) return !semver.satisfies(latestVersion.value, cachedVersion) - return semver.lt(cachedVersion, latestVersion) + return semver.lt(cachedVersion, latestVersion.value) }) const add = Effect.fn("Npm.add")(function* (pkg: string) { @@ -304,6 +321,7 @@ export const defaultLayer = layer.pipe( Layer.provide(AppFileSystem.layer), Layer.provide(Global.layer), Layer.provide(NodeFileSystem.layer), + Layer.provide(CrossSpawnSpawner.defaultLayer), ) const { runPromise } = makeRuntime(Service, defaultLayer) diff --git a/packages/opencode/test/installation/installation.test.ts b/packages/opencode/test/installation/installation.test.ts index 2b04c3858..0d3e92989 100644 --- a/packages/opencode/test/installation/installation.test.ts +++ b/packages/opencode/test/installation/installation.test.ts @@ -3,6 +3,7 @@ import { Effect, Layer, Stream } from "effect" import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { Installation } from "../../src/installation" +import { InstallationChannel } from "../../src/installation/version" const encoder = new TextEncoder() @@ -68,11 +69,15 @@ describe("installation", () => { expect(result).toBe("4.0.0-beta.1") }) - test("reads npm registry versions", async () => { + test("reads npm versions via npm view", async () => { + const calls: string[][] = [] const layer = testLayer( - () => jsonResponse({ version: "1.5.0" }), + () => { + throw new Error("unexpected http request") + }, (cmd, args) => { - if (cmd === "npm" && args.includes("registry")) return "https://registry.npmjs.org\n" + calls.push([cmd, ...args]) + if (cmd === "npm" && args[0] === "view") return '"1.5.0"\n' return "" }, ) @@ -81,18 +86,47 @@ describe("installation", () => { Installation.Service.use((svc) => svc.latest("npm")).pipe(Effect.provide(layer)), ) expect(result).toBe("1.5.0") + expect(calls).toContainEqual(["npm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"]) }) - test("reads npm registry versions for bun method", async () => { + test("reads npm versions via bun pm view", async () => { + const calls: string[][] = [] const layer = testLayer( - () => jsonResponse({ version: "1.6.0" }), - () => "", + () => { + throw new Error("unexpected http request") + }, + (cmd, args) => { + calls.push([cmd, ...args]) + if (cmd === "bun" && args[0] === "pm") return '"1.6.0"\n' + return "" + }, ) const result = await Effect.runPromise( Installation.Service.use((svc) => svc.latest("bun")).pipe(Effect.provide(layer)), ) expect(result).toBe("1.6.0") + expect(calls).toContainEqual(["bun", "pm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"]) + }) + + test("reads npm versions via pnpm view", async () => { + const calls: string[][] = [] + const layer = testLayer( + () => { + throw new Error("unexpected http request") + }, + (cmd, args) => { + calls.push([cmd, ...args]) + if (cmd === "pnpm" && args[0] === "view") return '"1.7.0"\n' + return "" + }, + ) + + const result = await Effect.runPromise( + Installation.Service.use((svc) => svc.latest("pnpm")).pipe(Effect.provide(layer)), + ) + expect(result).toBe("1.7.0") + expect(calls).toContainEqual(["pnpm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"]) }) test("reads scoop manifest versions", async () => { diff --git a/packages/opencode/test/npm.test.ts b/packages/opencode/test/npm.test.ts index a8ec92c2a..b7680bb70 100644 --- a/packages/opencode/test/npm.test.ts +++ b/packages/opencode/test/npm.test.ts @@ -1,10 +1,50 @@ import fs from "fs/promises" import path from "path" import { describe, expect, test } from "bun:test" +import { Effect, Layer, Stream } from "effect" +import { NodeFileSystem } from "@effect/platform-node" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { Global } from "@opencode-ai/shared/global" +import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" +import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { Npm } from "../src/npm" import { tmpdir } from "./fixture/fixture" const win = process.platform === "win32" +const encoder = new TextEncoder() +function mockSpawner(handler: (cmd: string, args: readonly string[]) => string = () => "") { + const spawner = ChildProcessSpawner.make((command) => { + const std = ChildProcess.isStandardCommand(command) ? command : undefined + const output = handler(std?.command ?? "", std?.args ?? []) + return Effect.succeed( + ChildProcessSpawner.makeHandle({ + pid: ChildProcessSpawner.ProcessId(0), + exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)), + isRunning: Effect.succeed(false), + kill: () => Effect.void, + stdin: { [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") } as any, + stdout: output ? Stream.make(encoder.encode(output)) : Stream.empty, + stderr: Stream.empty, + all: Stream.empty, + getInputFd: () => ({ [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") }) as any, + getOutputFd: () => Stream.empty, + unref: Effect.succeed(Effect.void), + }), + ) + }) + return Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner) +} + +function testLayer(spawnHandler?: (cmd: string, args: readonly string[]) => string) { + return Npm.layer.pipe( + Layer.provide(mockSpawner(spawnHandler)), + Layer.provide(EffectFlock.layer), + Layer.provide(AppFileSystem.layer), + Layer.provide(Global.layer), + Layer.provide(NodeFileSystem.layer), + ) +} + const writePackage = (dir: string, pkg: Record) => Bun.write( path.join(dir, "package.json"), @@ -53,3 +93,47 @@ describe("Npm.install", () => { await expect(fs.stat(path.join(tmp.path, "node_modules", "dev-pkg"))).rejects.toThrow() }) }) + +describe("Npm.outdated", () => { + test("checks latest via npm view", async () => { + const calls: string[][] = [] + const layer = testLayer((cmd, args) => { + calls.push([cmd, ...args]) + if (cmd === "npm" && args[0] === "view") return '"2.0.0"\n' + return "" + }) + + const result = await Effect.runPromise(Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer))) + + expect(result).toBe(true) + expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"]) + }) + + test("keeps range comparison behavior", async () => { + const layer = testLayer((cmd, args) => { + if (cmd === "npm" && args[0] === "view") return '"2.3.0"\n' + return "" + }) + + const result = await Effect.runPromise( + Npm.Service.use((svc) => svc.outdated("example", "^2.0.0")).pipe(Effect.provide(layer)), + ) + + expect(result).toBe(false) + }) + + test("falls back when npm view is unavailable", async () => { + const calls: string[][] = [] + const layer = testLayer((cmd, args) => { + calls.push([cmd, ...args]) + if (cmd === "pnpm" && args[0] === "view") return '"2.0.0"\n' + return "" + }) + + const result = await Effect.runPromise(Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer))) + + expect(result).toBe(true) + expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"]) + expect(calls).toContainEqual(["pnpm", "view", "example", "dist-tags.latest", "--json"]) + }) +}) From 353532b1c16bd5677efd2c352f502178f0c5094c Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 16:00:01 +0000 Subject: [PATCH 083/426] chore: generate --- packages/opencode/src/installation/index.ts | 4 ++- packages/opencode/src/npm/index.ts | 35 ++++++++++----------- packages/opencode/test/npm.test.ts | 8 +++-- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index e39b14b8f..787f9ea8c 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -136,7 +136,9 @@ export const layer: Layer.Layer path.join(global.cache, "packages", sanitize(pkg)) - const runView = Effect.fnUntraced( - function* (cmd: string[]) { - const handle = yield* spawner.spawn( - ChildProcess.make(cmd[0], cmd.slice(1), { - extendEnv: true, - }), - ) - const [stdout, stderr] = yield* Effect.all( - [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], - { concurrency: 2 }, - ) - const code = yield* handle.exitCode - if (code !== 0 || !stdout.trim()) { - return yield* Effect.fail(stderr || stdout || `Failed to run ${cmd.join(" ")}`) - } - return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.String))(stdout) - }, - Effect.scoped, - ) + const runView = Effect.fnUntraced(function* (cmd: string[]) { + const handle = yield* spawner.spawn( + ChildProcess.make(cmd[0], cmd.slice(1), { + extendEnv: true, + }), + ) + const [stdout, stderr] = yield* Effect.all( + [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], + { concurrency: 2 }, + ) + const code = yield* handle.exitCode + if (code !== 0 || !stdout.trim()) { + return yield* Effect.fail(stderr || stdout || `Failed to run ${cmd.join(" ")}`) + } + return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.String))(stdout) + }, Effect.scoped) const viewLatestVersion = Effect.fnUntraced(function* (pkg: string) { return yield* runView(["npm", "view", pkg, "dist-tags.latest", "--json"]).pipe( Effect.catch(() => diff --git a/packages/opencode/test/npm.test.ts b/packages/opencode/test/npm.test.ts index b7680bb70..b27d668c8 100644 --- a/packages/opencode/test/npm.test.ts +++ b/packages/opencode/test/npm.test.ts @@ -103,7 +103,9 @@ describe("Npm.outdated", () => { return "" }) - const result = await Effect.runPromise(Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer))) + const result = await Effect.runPromise( + Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer)), + ) expect(result).toBe(true) expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"]) @@ -130,7 +132,9 @@ describe("Npm.outdated", () => { return "" }) - const result = await Effect.runPromise(Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer))) + const result = await Effect.runPromise( + Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer)), + ) expect(result).toBe(true) expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"]) From c50d65b4d6b956cad44663d9d31b7b5eb01c8e57 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 12:43:08 -0400 Subject: [PATCH 084/426] refactor(sync): make session events schema-first (#24019) --- packages/opencode/src/bus/index.ts | 24 ++++++-- packages/opencode/src/server/projectors.ts | 3 +- packages/opencode/src/session/message-v2.ts | 44 ++++++++------ packages/opencode/src/session/projectors.ts | 2 +- packages/opencode/src/session/session.ts | 65 ++++++++++++++------- packages/opencode/src/share/share-next.ts | 2 +- packages/opencode/src/sync/index.ts | 44 ++++++++++---- packages/opencode/test/sync/index.test.ts | 6 +- packages/sdk/js/src/v2/gen/types.gen.ts | 32 +++++----- 9 files changed, 141 insertions(+), 81 deletions(-) diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index 8a9579b59..a2f9f5ccb 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -1,5 +1,5 @@ import z from "zod" -import { Effect, Exit, Layer, PubSub, Scope, Context, Stream } from "effect" +import { Effect, Exit, Layer, PubSub, Scope, Context, Stream, Schema as EffectSchema, Types } from "effect" import { EffectBridge } from "@/effect" import { Log } from "../util" import { BusEvent } from "./bus-event" @@ -9,6 +9,12 @@ import { makeRuntime } from "@/effect/run-service" const log = Log.create({ service: "bus" }) +type BusProperties = D extends { + effectProperties: infer Properties extends EffectSchema.Top +} + ? Types.DeepMutable> + : z.infer + export const InstanceDisposed = BusEvent.define( "server.instance.disposed", z.object({ @@ -18,7 +24,7 @@ export const InstanceDisposed = BusEvent.define( type Payload = { type: D["type"] - properties: z.infer + properties: BusProperties } type State = { @@ -29,7 +35,7 @@ type State = { export interface Interface { readonly publish: ( def: D, - properties: z.output, + properties: BusProperties, ) => Effect.Effect readonly subscribe: (def: D) => Stream.Stream> readonly subscribeAll: () => Stream.Stream @@ -79,7 +85,10 @@ export const layer = Layer.effect( }) } - function publish(def: D, properties: z.output) { + function publish( + def: D, + properties: BusProperties, + ) { return Effect.gen(function* () { const s = yield* InstanceState.get(state) const payload: Payload = { type: def.type, properties } @@ -175,13 +184,16 @@ const { runPromise, runSync } = makeRuntime(Service, layer) // runSync is safe here because the subscribe chain (InstanceState.get, PubSub.subscribe, // Scope.make, Effect.forkScoped) is entirely synchronous. If any step becomes async, this will throw. -export async function publish(def: D, properties: z.output) { +export async function publish( + def: D, + properties: BusProperties, +) { return runPromise((svc) => svc.publish(def, properties)) } export function subscribe( def: D, - callback: (event: { type: D["type"]; properties: z.infer }) => unknown, + callback: (event: Payload) => unknown, ) { return runSync((svc) => svc.subscribeCallback(def, callback)) } diff --git a/packages/opencode/src/server/projectors.ts b/packages/opencode/src/server/projectors.ts index cfecce526..18c273d58 100644 --- a/packages/opencode/src/server/projectors.ts +++ b/packages/opencode/src/server/projectors.ts @@ -1,4 +1,3 @@ -import z from "zod" import sessionProjectors from "../session/projectors" import { SyncEvent } from "@/sync" import { Session } from "@/session" @@ -10,7 +9,7 @@ export function initProjectors() { projectors: sessionProjectors, convertEvent: (type, data) => { if (type === "session.updated") { - const id = (data as z.infer).sessionID + const id = (data as SyncEvent.Event["data"]).sessionID const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get()) if (!row) return data diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 980dd4da8..a4b25d95e 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -576,34 +576,46 @@ export const Info = Object.assign(_Info, { }) export type Info = User | Assistant +const UpdatedEventSchema = Schema.Struct({ + sessionID: SessionID, + info: _Info, +}) + +const RemovedEventSchema = Schema.Struct({ + sessionID: SessionID, + messageID: MessageID, +}) + +const PartUpdatedEventSchema = Schema.Struct({ + sessionID: SessionID, + part: _Part, + time: Schema.Number, +}) + +const PartRemovedEventSchema = Schema.Struct({ + sessionID: SessionID, + messageID: MessageID, + partID: PartID, +}) + export const Event = { Updated: SyncEvent.define({ type: "message.updated", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - info: Info.zod, - }), + schema: UpdatedEventSchema, }), Removed: SyncEvent.define({ type: "message.removed", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod, - }), + schema: RemovedEventSchema, }), PartUpdated: SyncEvent.define({ type: "message.part.updated", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - part: Part.zod, - time: z.number(), - }), + schema: PartUpdatedEventSchema, }), PartDelta: BusEvent.define( "message.part.delta", @@ -619,11 +631,7 @@ export const Event = { type: "message.part.removed", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod, - partID: PartID.zod, - }), + schema: PartRemovedEventSchema, }), } diff --git a/packages/opencode/src/session/projectors.ts b/packages/opencode/src/session/projectors.ts index 7b3b0a7f2..3a5fd0d8c 100644 --- a/packages/opencode/src/session/projectors.ts +++ b/packages/opencode/src/session/projectors.ts @@ -71,7 +71,7 @@ export default [ const info = data.info const row = db .update(SessionTable) - .set(toPartialRow(info)) + .set(toPartialRow(info as Session.Patch)) .where(eq(SessionTable.id, data.sessionID)) .returning() .get() diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index d2bdbccb7..1e046fdf7 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -15,7 +15,6 @@ import { PartTable, SessionTable } from "./session.sql" import { ProjectTable } from "../project/project.sql" import { Storage } from "@/storage" import { Log } from "../util" -import { updateSchema } from "../util/update-schema" import { MessageV2 } from "./message-v2" import { Instance } from "../project/instance" import { InstanceState } from "@/effect" @@ -28,7 +27,7 @@ import type { Provider } from "@/provider" import { Permission } from "@/permission" import { Global } from "@/global" import { Effect, Layer, Option, Context, Schema, Types } from "effect" -import { zod, zodObject } from "@/util/effect-zod" +import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" const log = Log.create({ service: "session" }) @@ -215,40 +214,62 @@ export const MessagesInput = Schema.Struct({ limit: Schema.optional(Schema.Number), }).pipe(withStatics((s) => ({ zod: zod(s) }))) +const CreatedEventSchema = Schema.Struct({ + sessionID: SessionID, + info: Info, +}) + +const UpdatedShare = Schema.Struct({ + url: Schema.optional(Schema.NullOr(Schema.String)), +}) + +const UpdatedTime = Schema.Struct({ + created: Schema.optional(Schema.NullOr(Schema.Number)), + updated: Schema.optional(Schema.NullOr(Schema.Number)), + compacting: Schema.optional(Schema.NullOr(Schema.Number)), + archived: Schema.optional(Schema.NullOr(Schema.Number)), +}) + +const UpdatedInfo = Schema.Struct({ + id: Schema.optional(Schema.NullOr(SessionID)), + slug: Schema.optional(Schema.NullOr(Schema.String)), + projectID: Schema.optional(Schema.NullOr(ProjectID)), + workspaceID: Schema.optional(Schema.NullOr(WorkspaceID)), + directory: Schema.optional(Schema.NullOr(Schema.String)), + parentID: Schema.optional(Schema.NullOr(SessionID)), + summary: Schema.optional(Schema.NullOr(Summary)), + share: Schema.optional(UpdatedShare), + title: Schema.optional(Schema.NullOr(Schema.String)), + version: Schema.optional(Schema.NullOr(Schema.String)), + time: Schema.optional(UpdatedTime), + permission: Schema.optional(Schema.NullOr(Permission.Ruleset)), + revert: Schema.optional(Schema.NullOr(Revert)), +}) + +const UpdatedEventSchema = Schema.Struct({ + sessionID: SessionID, + info: UpdatedInfo, +}) + export const Event = { Created: SyncEvent.define({ type: "session.created", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - info: Info.zod, - }), + schema: CreatedEventSchema, }), Updated: SyncEvent.define({ type: "session.updated", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - info: updateSchema(zodObject(Info)).extend({ - share: updateSchema(zodObject(Share)).optional(), - time: updateSchema(zodObject(Time)).optional(), - }), - }), - busSchema: z.object({ - sessionID: SessionID.zod, - info: Info.zod, - }), + schema: UpdatedEventSchema, + busSchema: CreatedEventSchema, }), Deleted: SyncEvent.define({ type: "session.deleted", version: 1, aggregate: "sessionID", - schema: z.object({ - sessionID: SessionID.zod, - info: Info.zod, - }), + schema: CreatedEventSchema, }), Diff: BusEvent.define( "session.diff", @@ -394,7 +415,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/Session") {} -type Patch = z.infer["info"] +export type Patch = Types.DeepMutable["data"]["info"]> const db = (fn: (d: Parameters[0] extends (trx: infer D) => any ? D : never) => T) => Effect.sync(() => Database.use(fn)) diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts index 2622f4f7f..f26a085c2 100644 --- a/packages/opencode/src/share/share-next.ts +++ b/packages/opencode/src/share/share-next.ts @@ -181,7 +181,7 @@ export const layer = Layer.effect( yield* watch(Session.Event.Updated, (evt) => Effect.gen(function* () { - const info = yield* session.get(evt.properties.sessionID) + const info = evt.properties.info yield* sync(info.id, [{ type: "session", data: info }]) }), ) diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index 125d8c955..5a4078402 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -1,5 +1,4 @@ import z from "zod" -import type { ZodObject } from "zod" import { Database, eq } from "@/storage" import { GlobalBus } from "@/bus/global" import { Bus as ProjectBus } from "@/bus" @@ -9,11 +8,16 @@ import { EventSequenceTable, EventTable } from "./event.sql" import { WorkspaceContext } from "@/control-plane/workspace-context" import { EventID } from "./schema" import { Flag } from "@/flag/flag" +import { Schema as EffectSchema, Types } from "effect" +import { zodObject } from "@/util/effect-zod" +import { isRecord } from "@/util/record" -export type Definition = { +export type Definition = { type: string version: number aggregate: string + effectSchema: Schema + effectProperties: BusSchema schema: z.ZodObject // This is temporary and only exists for compatibility with bus @@ -25,9 +29,13 @@ export type Event = { id: string seq: number aggregateID: string - data: z.infer + data: Types.DeepMutable> } +export type Properties = Types.DeepMutable< + EffectSchema.Schema.Type +> + export type SerializedEvent = Event & { type: string } type ProjectorFunc = (db: Database.TxOrDb, data: unknown) => void @@ -36,7 +44,12 @@ export const registry = new Map() let projectors: Map | undefined const versions = new Map() let frozen = false -let convertEvent: (type: string, event: Event["data"]) => Promise> | Record +let convertEvent: (type: string, event: Event["data"]) => Promise | unknown + +function asRecord(input: unknown) { + if (isRecord(input)) return input + throw new Error(`SyncEvent.convertEvent must return an object, got: ${JSON.stringify(input)}`) +} export function reset() { frozen = false @@ -54,7 +67,7 @@ export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; co for (let [type, version] of versions.entries()) { let def = registry.get(versionedType(type, version))! - BusEvent.define(def.type, def.properties || def.schema) + BusEvent.define(def.type, def.properties) } // Freeze the system so it clearly errors if events are defined @@ -72,19 +85,26 @@ export function versionedType(type: string, version?: number) { export function define< Type extends string, Agg extends string, - Schema extends ZodObject>>, - BusSchema extends ZodObject = Schema, ->(input: { type: Type; version: number; aggregate: Agg; schema: Schema; busSchema?: BusSchema }) { + Schema extends EffectSchema.Top, + BusSchema extends EffectSchema.Top = Schema, +>(input: { type: Type; version: number; aggregate: Agg; schema: Schema; busSchema?: BusSchema }): Definition< + Schema, + BusSchema +> { if (frozen) { throw new Error("Error defining sync event: sync system has been frozen") } + const effectProperties = (input.busSchema ?? input.schema) as BusSchema + const def = { type: input.type, version: input.version, aggregate: input.aggregate, - schema: input.schema, - properties: input.busSchema ? input.busSchema : input.schema, + effectSchema: input.schema, + effectProperties, + schema: zodObject(input.schema), + properties: zodObject(effectProperties), } versions.set(def.type, Math.max(def.version, versions.get(def.type) || 0)) @@ -143,10 +163,10 @@ function process(def: Def, event: Event, options: { const result = convertEvent(def.type, event.data) if (result instanceof Promise) { void result.then((data) => { - void ProjectBus.publish({ type: def.type, properties: def.schema }, data) + void ProjectBus.publish({ type: def.type, properties: def.properties }, asRecord(data)) }) } else { - void ProjectBus.publish({ type: def.type, properties: def.schema }, result) + void ProjectBus.publish({ type: def.type, properties: def.properties }, asRecord(result)) } GlobalBus.emit("event", { diff --git a/packages/opencode/test/sync/index.test.ts b/packages/opencode/test/sync/index.test.ts index 866bcaa31..d50f0d7c9 100644 --- a/packages/opencode/test/sync/index.test.ts +++ b/packages/opencode/test/sync/index.test.ts @@ -1,6 +1,6 @@ import { describe, test, expect, beforeEach, afterEach, afterAll } from "bun:test" import { tmpdir } from "../fixture/fixture" -import z from "zod" +import { Schema } from "effect" import { Bus } from "../../src/bus" import { Instance } from "../../src/project/instance" import { SyncEvent } from "../../src/sync" @@ -43,13 +43,13 @@ describe("SyncEvent", () => { type: "item.created", version: 1, aggregate: "id", - schema: z.object({ id: z.string(), name: z.string() }), + schema: Schema.Struct({ id: Schema.String, name: Schema.String }), }) const Sent = SyncEvent.define({ type: "item.sent", version: 1, aggregate: "item_id", - schema: z.object({ item_id: z.string(), to: z.string() }), + schema: Schema.Struct({ item_id: Schema.String, to: Schema.String }), }) SyncEvent.init({ diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 1fcab2eda..d28ce2579 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1058,31 +1058,31 @@ export type SyncEventSessionUpdated = { data: { sessionID: string info: { - id: string | null - slug: string | null - projectID: string | null - workspaceID: string | null - directory: string | null - parentID: string | null - summary: { + id?: string | null + slug?: string | null + projectID?: string | null + workspaceID?: string | null + directory?: string | null + parentID?: string | null + summary?: { additions: number deletions: number files: number diffs?: Array } | null share?: { - url: string | null + url?: string | null } - title: string | null - version: string | null + title?: string | null + version?: string | null time?: { - created: number | null - updated: number | null - compacting: number | null - archived: number | null + created?: number | null + updated?: number | null + compacting?: number | null + archived?: number | null } - permission: PermissionRuleset | null - revert: { + permission?: PermissionRuleset | null + revert?: { messageID: string partID?: string snapshot?: string From aed03078f88af5c28035a28b6edbbf10228a49ed Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 16:44:32 +0000 Subject: [PATCH 085/426] chore: generate --- packages/opencode/src/bus/index.ts | 20 ++++---------------- packages/opencode/src/sync/index.ts | 16 +++++++++++----- packages/sdk/openapi.json | 21 +++------------------ 3 files changed, 18 insertions(+), 39 deletions(-) diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index a2f9f5ccb..9183ff72a 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -33,10 +33,7 @@ type State = { } export interface Interface { - readonly publish: ( - def: D, - properties: BusProperties, - ) => Effect.Effect + readonly publish: (def: D, properties: BusProperties) => Effect.Effect readonly subscribe: (def: D) => Stream.Stream> readonly subscribeAll: () => Stream.Stream readonly subscribeCallback: ( @@ -85,10 +82,7 @@ export const layer = Layer.effect( }) } - function publish( - def: D, - properties: BusProperties, - ) { + function publish(def: D, properties: BusProperties) { return Effect.gen(function* () { const s = yield* InstanceState.get(state) const payload: Payload = { type: def.type, properties } @@ -184,17 +178,11 @@ const { runPromise, runSync } = makeRuntime(Service, layer) // runSync is safe here because the subscribe chain (InstanceState.get, PubSub.subscribe, // Scope.make, Effect.forkScoped) is entirely synchronous. If any step becomes async, this will throw. -export async function publish( - def: D, - properties: BusProperties, -) { +export async function publish(def: D, properties: BusProperties) { return runPromise((svc) => svc.publish(def, properties)) } -export function subscribe( - def: D, - callback: (event: Payload) => unknown, -) { +export function subscribe(def: D, callback: (event: Payload) => unknown) { return runSync((svc) => svc.subscribeCallback(def, callback)) } diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index 5a4078402..482ad4fbb 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -12,7 +12,10 @@ import { Schema as EffectSchema, Types } from "effect" import { zodObject } from "@/util/effect-zod" import { isRecord } from "@/util/record" -export type Definition = { +export type Definition< + Schema extends EffectSchema.Top = EffectSchema.Top, + BusSchema extends EffectSchema.Top = Schema, +> = { type: string version: number aggregate: string @@ -87,10 +90,13 @@ export function define< Agg extends string, Schema extends EffectSchema.Top, BusSchema extends EffectSchema.Top = Schema, ->(input: { type: Type; version: number; aggregate: Agg; schema: Schema; busSchema?: BusSchema }): Definition< - Schema, - BusSchema -> { +>(input: { + type: Type + version: number + aggregate: Agg + schema: Schema + busSchema?: BusSchema +}): Definition { if (frozen) { throw new Error("Error defining sync event: sync system has been frozen") } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 55570d47d..e54648e19 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -10570,8 +10570,7 @@ } ] } - }, - "required": ["url"] + } }, "title": { "anyOf": [ @@ -10636,8 +10635,7 @@ } ] } - }, - "required": ["created", "updated", "compacting", "archived"] + } }, "permission": { "anyOf": [ @@ -10676,20 +10674,7 @@ } ] } - }, - "required": [ - "id", - "slug", - "projectID", - "workspaceID", - "directory", - "parentID", - "summary", - "title", - "version", - "permission", - "revert" - ] + } } }, "required": ["sessionID", "info"] From 8b2f8355b22414a768c4a594d7a7101a4f72b00f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 12:54:46 -0400 Subject: [PATCH 086/426] docs(schema): mark sync/index.ts migrated with compat-bridge note (#24024) --- packages/opencode/specs/effect/schema.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 3f2c3b4c9..0abdd9ed6 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -147,6 +147,17 @@ import `z` do so only for local `ZodOverride` bridges or for `z.ZodType` type annotations — the `export const ` values are all Effect Schema at source. +A file is considered "done" when: + +- its exported schema values (`Info`, `Input`, `Event`, `Definition`, etc.) + are authored as Effect Schema +- any remaining zod is either a derived compat bridge (via `zod()` / + `zodObject()`), a `z.ZodType` type annotation, or a documented + `ZodOverride` escape hatch — never a hand-written parallel source of truth + +Files that meet this bar but still carry a compat bridge are checked off +with an inline note describing the bridge and what unblocks its removal. + - [x] skills, formatter, console-state, mcp, lsp, permission (leaves), model-id, command, plugin, provider - [x] server, layout - [x] keybinds @@ -365,7 +376,7 @@ piecewise. - [ ] `src/snapshot/index.ts` - [ ] `src/storage/db.ts` - [ ] `src/storage/storage.ts` -- [ ] `src/sync/index.ts` +- [x] `src/sync/index.ts` — public API (`SyncEvent.define`) is Schema-first; still stores derived zod `schema`/`properties` on each `Definition` as a compat bridge for `BusEvent` until `bus/bus-event.ts` migrates - [ ] `src/util/fn.ts` - [ ] `src/util/log.ts` - [ ] `src/util/update-schema.ts` From 1e439b8226a66dcec0d65751f5cdb50a2b7bc979 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 23 Apr 2026 13:13:26 -0400 Subject: [PATCH 087/426] sync --- packages/console/app/src/routes/zen/util/handler.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index d9dc45001..87d4a4d0a 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -187,6 +187,8 @@ export async function handler( // Try another provider => stop retrying if using fallback provider if ( res.status !== 200 && + // ie. 400 error is usually provider error like malformed request + res.status !== 400 && // ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found. res.status !== 404 && // ie. cannot change codex model providers mid-session @@ -226,7 +228,7 @@ export async function handler( logger.debug("STATUS: " + res.status + " " + res.statusText) // Handle non-streaming response - if (!isStream || res.status === 429) { + if (!isStream || [400, 404, 429].includes(res.status)) { const json = await res.json() await rateLimiter?.track() if (json.usage) { @@ -238,6 +240,9 @@ export async function handler( await reload(billingSource, authInfo, costInfo) json.cost = calculateOccurredCost(billingSource, costInfo) } + if (res.status === 400) { + logger.metric({ "error.response": JSON.stringify(json) }) + } if (json.error?.message) { json.error.message = `Error from provider${providerInfo.displayName ? ` (${providerInfo.displayName})` : ""}: ${json.error.message}` } @@ -393,7 +398,7 @@ export async function handler( type: "error", error: { type: "error", - message: error.message, + message: "Internal server error", }, }), { status: 500 }, From 93940a1859530b8fe46df376e7d7ba74fcd3e0d1 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 13:17:33 -0400 Subject: [PATCH 088/426] refactor(provider): migrate provider domain to Effect Schema (#24027) --- packages/opencode/specs/effect/schema.md | 6 +- packages/opencode/src/provider/auth.ts | 23 ++-- packages/opencode/src/provider/models.ts | 150 ++++++++++----------- packages/opencode/src/provider/provider.ts | 27 ++-- 4 files changed, 93 insertions(+), 113 deletions(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 0abdd9ed6..0d451f4a9 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -274,9 +274,9 @@ Possible later tightening after the Schema-first migration is stable: ### Provider domain -- [ ] `src/provider/auth.ts` -- [ ] `src/provider/models.ts` -- [ ] `src/provider/provider.ts` +- [x] `src/provider/auth.ts` +- [x] `src/provider/models.ts` +- [x] `src/provider/provider.ts` ### Tool schemas diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index 5d8b2765d..0b4ac995a 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -1,13 +1,12 @@ import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin" -import { NamedError } from "@opencode-ai/shared/util/error" import { Auth } from "@/auth" import { InstanceState } from "@/effect" import { zod } from "@/util/effect-zod" +import { namedSchemaError } from "@/util/named-schema-error" import { withStatics } from "@/util/schema" import { Plugin } from "../plugin" import { ProviderID } from "./schema" import { Array as Arr, Effect, Layer, Record, Result, Context, Schema } from "effect" -import z from "zod" const When = Schema.Struct({ key: Schema.String, @@ -70,22 +69,16 @@ export const CallbackInput = Schema.Struct({ }).pipe(withStatics((s) => ({ zod: zod(s) }))) export type CallbackInput = Schema.Schema.Type -export const OauthMissing = NamedError.create("ProviderAuthOauthMissing", z.object({ providerID: ProviderID.zod })) +export const OauthMissing = namedSchemaError("ProviderAuthOauthMissing", { providerID: ProviderID }) -export const OauthCodeMissing = NamedError.create( - "ProviderAuthOauthCodeMissing", - z.object({ providerID: ProviderID.zod }), -) +export const OauthCodeMissing = namedSchemaError("ProviderAuthOauthCodeMissing", { providerID: ProviderID }) -export const OauthCallbackFailed = NamedError.create("ProviderAuthOauthCallbackFailed", z.object({})) +export const OauthCallbackFailed = namedSchemaError("ProviderAuthOauthCallbackFailed", {}) -export const ValidationFailed = NamedError.create( - "ProviderAuthValidationFailed", - z.object({ - field: z.string(), - message: z.string(), - }), -) +export const ValidationFailed = namedSchemaError("ProviderAuthValidationFailed", { + field: Schema.String, + message: Schema.String, +}) export type Error = | Auth.AuthError diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index 2924666c0..36c4d8c23 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -1,7 +1,7 @@ import { Global } from "../global" import { Log } from "../util" import path from "path" -import z from "zod" +import { Schema } from "effect" import { Installation } from "../installation" import { Flag } from "../flag/flag" import { lazy } from "@/util/lazy" @@ -21,91 +21,85 @@ const filepath = path.join( ) const ttl = 5 * 60 * 1000 -type JsonValue = string | number | boolean | null | { [key: string]: JsonValue } | JsonValue[] - -const JsonValue: z.ZodType = z.lazy(() => - z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(JsonValue), z.record(z.string(), JsonValue)]), -) - -const Cost = z.object({ - input: z.number(), - output: z.number(), - cache_read: z.number().optional(), - cache_write: z.number().optional(), - context_over_200k: z - .object({ - input: z.number(), - output: z.number(), - cache_read: z.number().optional(), - cache_write: z.number().optional(), - }) - .optional(), +const Cost = Schema.Struct({ + input: Schema.Number, + output: Schema.Number, + cache_read: Schema.optional(Schema.Number), + cache_write: Schema.optional(Schema.Number), + context_over_200k: Schema.optional( + Schema.Struct({ + input: Schema.Number, + output: Schema.Number, + cache_read: Schema.optional(Schema.Number), + cache_write: Schema.optional(Schema.Number), + }), + ), }) -export const Model = z.object({ - id: z.string(), - name: z.string(), - family: z.string().optional(), - release_date: z.string(), - attachment: z.boolean(), - reasoning: z.boolean(), - temperature: z.boolean(), - tool_call: z.boolean(), - interleaved: z - .union([ - z.literal(true), - z - .object({ - field: z.enum(["reasoning_content", "reasoning_details"]), - }) - .strict(), - ]) - .optional(), - cost: Cost.optional(), - limit: z.object({ - context: z.number(), - input: z.number().optional(), - output: z.number(), +export const Model = Schema.Struct({ + id: Schema.String, + name: Schema.String, + family: Schema.optional(Schema.String), + release_date: Schema.String, + attachment: Schema.Boolean, + reasoning: Schema.Boolean, + temperature: Schema.Boolean, + tool_call: Schema.Boolean, + interleaved: Schema.optional( + Schema.Union([ + Schema.Literal(true), + Schema.Struct({ + field: Schema.Literals(["reasoning_content", "reasoning_details"]), + }), + ]), + ), + cost: Schema.optional(Cost), + limit: Schema.Struct({ + context: Schema.Number, + input: Schema.optional(Schema.Number), + output: Schema.Number, }), - modalities: z - .object({ - input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])), - output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])), - }) - .optional(), - experimental: z - .object({ - modes: z - .record( - z.string(), - z.object({ - cost: Cost.optional(), - provider: z - .object({ - body: z.record(z.string(), JsonValue).optional(), - headers: z.record(z.string(), z.string()).optional(), - }) - .optional(), + modalities: Schema.optional( + Schema.Struct({ + input: Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"])), + output: Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"])), + }), + ), + experimental: Schema.optional( + Schema.Struct({ + modes: Schema.optional( + Schema.Record( + Schema.String, + Schema.Struct({ + cost: Schema.optional(Cost), + provider: Schema.optional( + Schema.Struct({ + body: Schema.optional(Schema.Record(Schema.String, Schema.MutableJson)), + headers: Schema.optional(Schema.Record(Schema.String, Schema.String)), + }), + ), }), - ) - .optional(), - }) - .optional(), - status: z.enum(["alpha", "beta", "deprecated"]).optional(), - provider: z.object({ npm: z.string().optional(), api: z.string().optional() }).optional(), + ), + ), + }), + ), + status: Schema.optional(Schema.Literals(["alpha", "beta", "deprecated"])), + provider: Schema.optional( + Schema.Struct({ npm: Schema.optional(Schema.String), api: Schema.optional(Schema.String) }), + ), }) -export type Model = z.infer +export type Model = Schema.Schema.Type -export const Provider = z.object({ - api: z.string().optional(), - name: z.string(), - env: z.array(z.string()), - id: z.string(), - npm: z.string().optional(), - models: z.record(z.string(), Model), +export const Provider = Schema.Struct({ + api: Schema.optional(Schema.String), + name: Schema.String, + env: Schema.Array(Schema.String), + id: Schema.String, + npm: Schema.optional(Schema.String), + models: Schema.Record(Schema.String, Model), }) -export type Provider = z.infer +export type Provider = Schema.Schema.Type function url() { return Flag.OPENCODE_MODELS_URL || "https://models.dev" diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index d643f2537..d826f6b35 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1,4 +1,3 @@ -import z from "zod" import os from "os" import fuzzysort from "fuzzysort" import { Config } from "../config" @@ -8,7 +7,6 @@ import { Log } from "../util" import { Npm } from "../npm" import { Hash } from "@opencode-ai/shared/util/hash" import { Plugin } from "../plugin" -import { NamedError } from "@opencode-ai/shared/util/error" import { type LanguageModelV3 } from "@ai-sdk/provider" import * as ModelsDev from "./models" import { Auth } from "../auth" @@ -16,6 +14,7 @@ import { Env } from "../env" import { InstallationVersion } from "../installation/version" import { Flag } from "../flag/flag" import { zod } from "@/util/effect-zod" +import { namedSchemaError } from "@/util/named-schema-error" import { iife } from "@/util/iife" import { Global } from "../global" import path from "path" @@ -1047,7 +1046,7 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info { id: ProviderID.make(provider.id), source: "custom", name: provider.name, - env: provider.env ?? [], + env: [...(provider.env ?? [])], options: {}, models, } @@ -1713,18 +1712,12 @@ export function parseModel(model: string) { } } -export const ModelNotFoundError = NamedError.create( - "ProviderModelNotFoundError", - z.object({ - providerID: ProviderID.zod, - modelID: ModelID.zod, - suggestions: z.array(z.string()).optional(), - }), -) +export const ModelNotFoundError = namedSchemaError("ProviderModelNotFoundError", { + providerID: ProviderID, + modelID: ModelID, + suggestions: Schema.optional(Schema.Array(Schema.String)), +}) -export const InitError = NamedError.create( - "ProviderInitError", - z.object({ - providerID: ProviderID.zod, - }), -) +export const InitError = namedSchemaError("ProviderInitError", { + providerID: ProviderID, +}) From 0590452456a746457d5255e2ce3ecd9c5a4a621d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 13:22:48 -0400 Subject: [PATCH 089/426] refactor(schema): use Schema.Int and consolidate PositiveInt/NonNegativeInt (#24029) --- packages/opencode/src/config/agent.ts | 3 +-- packages/opencode/src/config/config.ts | 5 +---- packages/opencode/src/config/provider.ts | 4 +--- packages/opencode/src/config/server.ts | 4 ++-- packages/opencode/src/session/message-v2.ts | 20 +++++++++----------- packages/opencode/src/util/schema.ts | 10 ++++++++++ 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index 85021407c..2978916b5 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -4,6 +4,7 @@ import { Schema } from "effect" import z from "zod" import { Bus } from "@/bus" import { zod } from "@/util/effect-zod" +import { PositiveInt } from "@/util/schema" import { Log } from "../util" import { NamedError } from "@opencode-ai/shared/util/error" import { Glob } from "@opencode-ai/shared/util/glob" @@ -15,8 +16,6 @@ import { ConfigPermission } from "./permission" const log = Log.create({ service: "config" }) -const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) - const Color = Schema.Union([ Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)), Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]), diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 5423ba3ba..9814017d1 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -25,7 +25,7 @@ import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "e import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" import { InstanceRef } from "@/effect/instance-ref" import { zod, ZodOverride } from "@/util/effect-zod" -import { withStatics } from "@/util/schema" +import { NonNegativeInt, PositiveInt, withStatics } from "@/util/schema" import { ConfigAgent } from "./agent" import { ConfigCommand } from "./command" import { ConfigFormatter } from "./formatter" @@ -88,9 +88,6 @@ export type Layout = ConfigLayout.Layout const AgentRef = Schema.Any.annotate({ [ZodOverride]: ConfigAgent.Info }) const LogLevelRef = Schema.Any.annotate({ [ZodOverride]: Log.Level }) -const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) -const NonNegativeInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0)) - // The Effect Schema is the canonical source of truth. The `.zod` compatibility // surface is derived so existing Hono validators keep working without a parallel // Zod definition. diff --git a/packages/opencode/src/config/provider.ts b/packages/opencode/src/config/provider.ts index bd6ae3599..cd7469435 100644 --- a/packages/opencode/src/config/provider.ts +++ b/packages/opencode/src/config/provider.ts @@ -1,8 +1,6 @@ import { Schema } from "effect" import { zod } from "@/util/effect-zod" -import { withStatics } from "@/util/schema" - -const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) +import { PositiveInt, withStatics } from "@/util/schema" export const Model = Schema.Struct({ id: Schema.optional(Schema.String), diff --git a/packages/opencode/src/config/server.ts b/packages/opencode/src/config/server.ts index 3ce4fe626..3f1369826 100644 --- a/packages/opencode/src/config/server.ts +++ b/packages/opencode/src/config/server.ts @@ -1,9 +1,9 @@ import { Schema } from "effect" import { zod } from "@/util/effect-zod" -import { withStatics } from "@/util/schema" +import { PositiveInt, withStatics } from "@/util/schema" export const Server = Schema.Struct({ - port: Schema.optional(Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0))).annotate({ + port: Schema.optional(PositiveInt).annotate({ description: "Port to listen on", }), hostname: Schema.optional(Schema.String).annotate({ description: "Hostname to listen on" }), diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index a4b25d95e..c5a35c7b4 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -17,7 +17,7 @@ import type { Provider } from "@/provider" import { ModelID, ProviderID } from "@/provider/schema" import { Effect, Schema, Types } from "effect" import { zod, ZodOverride } from "@/util/effect-zod" -import { withStatics } from "@/util/schema" +import { NonNegativeInt, withStatics } from "@/util/schema" import { namedSchemaError } from "@/util/named-schema-error" import { EffectLogger } from "@/effect" @@ -64,9 +64,7 @@ export class OutputFormatText extends Schema.Class("OutputForm export class OutputFormatJsonSchema extends Schema.Class("OutputFormatJsonSchema")({ type: Schema.Literal("json_schema"), schema: Schema.Record(Schema.String, Schema.Any).annotate({ identifier: "JSONSchema" }), - retryCount: Schema.Number.check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)) - .pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))), + retryCount: NonNegativeInt.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(2))), }) { static readonly zod = zod(this) } @@ -138,8 +136,8 @@ export type ReasoningPart = Types.DeepMutable ({ zod: zod(s) }))) @@ -196,8 +194,8 @@ export const AgentPart = Schema.Struct({ source: Schema.optional( Schema.Struct({ value: Schema.String, - start: Schema.Number.check(Schema.isInt()), - end: Schema.Number.check(Schema.isInt()), + start: Schema.Int, + end: Schema.Int, }), ), }) @@ -501,8 +499,8 @@ export const AgentPartInput = Schema.Struct({ source: Schema.optional( Schema.Struct({ value: Schema.String, - start: Schema.Number.check(Schema.isInt()), - end: Schema.Number.check(Schema.isInt()), + start: Schema.Int, + end: Schema.Int, }), ), }) diff --git a/packages/opencode/src/util/schema.ts b/packages/opencode/src/util/schema.ts index 405f6a718..7f94ee5d1 100644 --- a/packages/opencode/src/util/schema.ts +++ b/packages/opencode/src/util/schema.ts @@ -1,5 +1,15 @@ import { Schema } from "effect" +/** + * Integer greater than zero. + */ +export const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0)) + +/** + * Integer greater than or equal to zero. + */ +export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)) + /** * Attach static methods to a schema object. Designed to be used with `.pipe()`: * From cd93533b1f5eae9362a485b0072f98a05d1ca161 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 15:37:44 -0400 Subject: [PATCH 090/426] refactor(bus): migrate BusEvent to Effect Schema (#24040) --- packages/opencode/src/bus/bus-event.ts | 20 +++-- packages/opencode/src/bus/index.ts | 13 +--- packages/opencode/src/cli/cmd/tui/event.ts | 26 +++---- .../opencode/src/cli/cmd/tui/ui/toast.tsx | 4 +- packages/opencode/src/command/index.ts | 12 +-- packages/opencode/src/config/config.ts | 25 +------ .../opencode/src/control-plane/workspace.ts | 28 +++---- packages/opencode/src/file/index.ts | 6 +- packages/opencode/src/file/watcher.ts | 8 +- packages/opencode/src/ide/index.ts | 5 +- packages/opencode/src/installation/index.ts | 8 +- packages/opencode/src/lsp/client.ts | 7 +- packages/opencode/src/lsp/lsp.ts | 2 +- packages/opencode/src/mcp/index.ts | 12 +-- packages/opencode/src/permission/index.ts | 14 ++-- packages/opencode/src/project/project.ts | 2 +- packages/opencode/src/project/vcs.ts | 6 +- packages/opencode/src/pty/index.ts | 75 ++++++++++--------- packages/opencode/src/question/index.ts | 8 +- packages/opencode/src/server/event.ts | 6 +- .../src/server/routes/control/workspace.ts | 3 +- packages/opencode/src/server/routes/global.ts | 4 +- .../src/server/routes/instance/pty.ts | 16 ++-- .../src/server/routes/instance/tui.ts | 22 +++--- packages/opencode/src/session/compaction.ts | 6 +- packages/opencode/src/session/message-v2.ts | 12 +-- packages/opencode/src/session/session.ts | 15 ++-- packages/opencode/src/session/status.ts | 10 +-- packages/opencode/src/session/todo.ts | 6 +- packages/opencode/src/sync/index.ts | 60 +++++++-------- packages/opencode/src/util/effect-zod.ts | 17 +++-- packages/opencode/src/util/schema.ts | 47 ++++++++++-- packages/opencode/src/worktree/index.ts | 12 +-- packages/opencode/test/bus/bus-effect.test.ts | 7 +- .../opencode/test/bus/bus-integration.test.ts | 6 +- packages/opencode/test/bus/bus.test.ts | 6 +- .../opencode/test/session/session.test.ts | 5 +- 37 files changed, 281 insertions(+), 260 deletions(-) diff --git a/packages/opencode/src/bus/bus-event.ts b/packages/opencode/src/bus/bus-event.ts index efaed9440..f27d26335 100644 --- a/packages/opencode/src/bus/bus-event.ts +++ b/packages/opencode/src/bus/bus-event.ts @@ -1,15 +1,19 @@ import z from "zod" -import type { ZodType } from "zod" +import { Schema } from "effect" +import { zodObject } from "@/util/effect-zod" -export type Definition = ReturnType +export type Definition = { + type: Type + properties: Properties +} const registry = new Map() -export function define(type: Type, properties: Properties) { - const result = { - type, - properties, - } +export function define( + type: Type, + properties: Properties, +): Definition { + const result = { type, properties } registry.set(type, result) return result } @@ -21,7 +25,7 @@ export function payloads() { return z .object({ type: z.literal(type), - properties: def.properties, + properties: zodObject(def.properties), }) .meta({ ref: `Event.${def.type}`, diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index 9183ff72a..12251f26c 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -1,5 +1,4 @@ -import z from "zod" -import { Effect, Exit, Layer, PubSub, Scope, Context, Stream, Schema as EffectSchema, Types } from "effect" +import { Effect, Exit, Layer, PubSub, Scope, Context, Stream, Schema } from "effect" import { EffectBridge } from "@/effect" import { Log } from "../util" import { BusEvent } from "./bus-event" @@ -9,16 +8,12 @@ import { makeRuntime } from "@/effect/run-service" const log = Log.create({ service: "bus" }) -type BusProperties = D extends { - effectProperties: infer Properties extends EffectSchema.Top -} - ? Types.DeepMutable> - : z.infer +type BusProperties> = Schema.Schema.Type export const InstanceDisposed = BusEvent.define( "server.instance.disposed", - z.object({ - directory: z.string(), + Schema.Struct({ + directory: Schema.String, }), ) diff --git a/packages/opencode/src/cli/cmd/tui/event.ts b/packages/opencode/src/cli/cmd/tui/event.ts index fa164d53e..ab85b1e64 100644 --- a/packages/opencode/src/cli/cmd/tui/event.ts +++ b/packages/opencode/src/cli/cmd/tui/event.ts @@ -1,14 +1,14 @@ import { BusEvent } from "@/bus/bus-event" import { SessionID } from "@/session/schema" -import z from "zod" +import { Schema } from "effect" export const TuiEvent = { - PromptAppend: BusEvent.define("tui.prompt.append", z.object({ text: z.string() })), + PromptAppend: BusEvent.define("tui.prompt.append", Schema.Struct({ text: Schema.String })), CommandExecute: BusEvent.define( "tui.command.execute", - z.object({ - command: z.union([ - z.enum([ + Schema.Struct({ + command: Schema.Union([ + Schema.Literals([ "session.list", "session.new", "session.share", @@ -26,23 +26,23 @@ export const TuiEvent = { "prompt.submit", "agent.cycle", ]), - z.string(), + Schema.String, ]), }), ), ToastShow: BusEvent.define( "tui.toast.show", - z.object({ - title: z.string().optional(), - message: z.string(), - variant: z.enum(["info", "success", "warning", "error"]), - duration: z.number().default(5000).optional().describe("Duration in milliseconds"), + Schema.Struct({ + title: Schema.optional(Schema.String), + message: Schema.String, + variant: Schema.Literals(["info", "success", "warning", "error"]), + duration: Schema.optional(Schema.Number).annotate({ description: "Duration in milliseconds" }), }), ), SessionSelect: BusEvent.define( "tui.session.select", - z.object({ - sessionID: SessionID.zod.describe("Session ID to navigate to"), + Schema.Struct({ + sessionID: SessionID.annotate({ description: "Session ID to navigate to" }), }), ), } diff --git a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx index f534d90b7..69674ba7c 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx @@ -4,10 +4,10 @@ import { useTheme } from "@tui/context/theme" import { useTerminalDimensions } from "@opentui/solid" import { SplitBorder } from "../component/border" import { TextAttributes } from "@opentui/core" -import z from "zod" +import { Schema } from "effect" import { type TuiEvent } from "../event" -export type ToastOptions = z.infer +export type ToastOptions = Schema.Schema.Type export function Toast() { const toast = useToast() diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 27ba357ec..478a12f66 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -3,7 +3,7 @@ import { InstanceState } from "@/effect" import { EffectBridge } from "@/effect" import type { InstanceContext } from "@/project/instance" import { SessionID, MessageID } from "@/session/schema" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema } from "effect" import z from "zod" import { Config } from "../config" import { MCP } from "../mcp" @@ -18,11 +18,11 @@ type State = { export const Event = { Executed: BusEvent.define( "command.executed", - z.object({ - name: z.string(), - sessionID: SessionID.zod, - arguments: z.string(), - messageID: MessageID.zod, + Schema.Struct({ + name: Schema.String, + sessionID: SessionID, + arguments: Schema.String, + messageID: MessageID, }), ), } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 9814017d1..eab199232 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -25,7 +25,7 @@ import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "e import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" import { InstanceRef } from "@/effect/instance-ref" import { zod, ZodOverride } from "@/util/effect-zod" -import { NonNegativeInt, PositiveInt, withStatics } from "@/util/schema" +import { NonNegativeInt, PositiveInt, withStatics, type DeepMutable } from "@/util/schema" import { ConfigAgent } from "./agent" import { ConfigCommand } from "./command" import { ConfigFormatter } from "./formatter" @@ -249,26 +249,9 @@ export const Info = Schema.Struct({ })), ) -// Schema.Struct produces readonly types by default, but the service code -// below mutates Info objects directly (e.g. `config.mode = ...`). Strip the -// readonly recursively so callers get the same mutable shape zod inferred. -// -// `Types.DeepMutable` from effect-smol would be a drop-in, but its fallback -// branch `{ -readonly [K in keyof T]: ... }` collapses `unknown` to `{}` -// (since `keyof unknown = never`), which widens `Record` -// fields like `ConfigPlugin.Options`. The local version gates on -// `extends object` so `unknown` passes through. -// -// Tuple branch preserves `ConfigPlugin.Spec`'s `readonly [string, Options]` -// shape (otherwise the general array branch widens it to an array). -type DeepMutable = T extends readonly [unknown, ...unknown[]] - ? { -readonly [K in keyof T]: DeepMutable } - : T extends readonly (infer U)[] - ? DeepMutable[] - : T extends object - ? { -readonly [K in keyof T]: DeepMutable } - : T - +// Uses the shared `DeepMutable` from `@/util/schema`. See the definition +// there for why the local variant is needed over `Types.DeepMutable` from +// effect-smol (the upstream version collapses `unknown` to `{}`). export type Info = DeepMutable> & { // plugin_origins is derived state, not a persisted config field. It keeps each winning plugin spec together // with the file and scope it came from so later runtime code can make location-sensitive decisions. diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index eb689df02..8519cb06c 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -1,4 +1,5 @@ import z from "zod" +import { Schema } from "effect" import { setTimeout as sleep } from "node:timers/promises" import { fn } from "@/util/fn" import { Database, asc, eq, inArray } from "@/storage" @@ -25,36 +26,37 @@ import { errorData } from "@/util/error" import { AppRuntime } from "@/effect/app-runtime" import { waitEvent } from "./util" import { WorkspaceContext } from "./workspace-context" +import { NonNegativeInt } from "@/util/schema" export const Info = WorkspaceInfo.meta({ ref: "Workspace", }) export type Info = z.infer -export const ConnectionStatus = z.object({ - workspaceID: WorkspaceID.zod, - status: z.enum(["connected", "connecting", "disconnected", "error"]), +export const ConnectionStatus = Schema.Struct({ + workspaceID: WorkspaceID, + status: Schema.Literals(["connected", "connecting", "disconnected", "error"]), }) -export type ConnectionStatus = z.infer +export type ConnectionStatus = Schema.Schema.Type -const Restore = z.object({ - workspaceID: WorkspaceID.zod, - sessionID: SessionID.zod, - total: z.number().int().min(0), - step: z.number().int().min(0), +const Restore = Schema.Struct({ + workspaceID: WorkspaceID, + sessionID: SessionID, + total: NonNegativeInt, + step: NonNegativeInt, }) export const Event = { Ready: BusEvent.define( "workspace.ready", - z.object({ - name: z.string(), + Schema.Struct({ + name: Schema.String, }), ), Failed: BusEvent.define( "workspace.failed", - z.object({ - message: z.string(), + Schema.Struct({ + message: Schema.String, }), ), Restore: BusEvent.define("workspace.restore", Restore), diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index af4fbf76c..ca791e412 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -3,7 +3,7 @@ import { InstanceState } from "@/effect" import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Git } from "@/git" -import { Effect, Layer, Context, Scope } from "effect" +import { Effect, Layer, Context, Schema, Scope } from "effect" import * as Stream from "effect/Stream" import { formatPatch, structuredPatch } from "diff" import fuzzysort from "fuzzysort" @@ -76,8 +76,8 @@ export type Content = z.infer export const Event = { Edited: BusEvent.define( "file.edited", - z.object({ - file: z.string(), + Schema.Struct({ + file: Schema.String, }), ), } diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts index dc2033375..0ac98b9c2 100644 --- a/packages/opencode/src/file/watcher.ts +++ b/packages/opencode/src/file/watcher.ts @@ -1,4 +1,4 @@ -import { Cause, Effect, Layer, Context } from "effect" +import { Cause, Effect, Layer, Context, Schema } from "effect" // @ts-ignore import { createWrapper } from "@parcel/watcher/wrapper" import type ParcelWatcher from "@parcel/watcher" @@ -25,9 +25,9 @@ const SUBSCRIBE_TIMEOUT_MS = 10_000 export const Event = { Updated: BusEvent.define( "file.watcher.updated", - z.object({ - file: z.string(), - event: z.union([z.literal("add"), z.literal("change"), z.literal("unlink")]), + Schema.Struct({ + file: Schema.String, + event: Schema.Literals(["add", "change", "unlink"]), }), ), } diff --git a/packages/opencode/src/ide/index.ts b/packages/opencode/src/ide/index.ts index ee80c3474..f9ce1ec63 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -1,5 +1,6 @@ import { BusEvent } from "@/bus/bus-event" import z from "zod" +import { Schema } from "effect" import { NamedError } from "@opencode-ai/shared/util/error" import { Log } from "../util" import { Process } from "@/util" @@ -17,8 +18,8 @@ const log = Log.create({ service: "ide" }) export const Event = { Installed: BusEvent.define( "ide.installed", - z.object({ - ide: z.string(), + Schema.Struct({ + ide: Schema.String, }), ), } diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 787f9ea8c..bb3de3f3b 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -21,14 +21,14 @@ export type ReleaseType = "patch" | "minor" | "major" export const Event = { Updated: BusEvent.define( "installation.updated", - z.object({ - version: z.string(), + Schema.Struct({ + version: Schema.String, }), ), UpdateAvailable: BusEvent.define( "installation.update-available", - z.object({ - version: z.string(), + Schema.Struct({ + version: Schema.String, }), ), } diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index f6d5110a6..e8050babf 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -8,6 +8,7 @@ import { Log } from "../util" import { Process } from "../util" import { LANGUAGE_EXTENSIONS } from "./language" import z from "zod" +import { Schema } from "effect" import type * as LSPServer from "./server" import { NamedError } from "@opencode-ai/shared/util/error" import { withTimeout } from "../util/timeout" @@ -41,9 +42,9 @@ export const InitializeError = NamedError.create( export const Event = { Diagnostics: BusEvent.define( "lsp.client.diagnostics", - z.object({ - serverID: z.string(), - path: z.string(), + Schema.Struct({ + serverID: Schema.String, + path: Schema.String, }), ), } diff --git a/packages/opencode/src/lsp/lsp.ts b/packages/opencode/src/lsp/lsp.ts index 4c46cd9aa..7741ff60e 100644 --- a/packages/opencode/src/lsp/lsp.ts +++ b/packages/opencode/src/lsp/lsp.ts @@ -19,7 +19,7 @@ import { zod, ZodOverride } from "@/util/effect-zod" const log = Log.create({ service: "lsp" }) export const Event = { - Updated: BusEvent.define("lsp.updated", z.object({})), + Updated: BusEvent.define("lsp.updated", Schema.Struct({})), } const Position = Schema.Struct({ diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 09fcfc756..385d7782a 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -25,7 +25,7 @@ import { BusEvent } from "../bus/bus-event" import { Bus } from "@/bus" import { TuiEvent } from "@/cli/cmd/tui/event" import open from "open" -import { Effect, Exit, Layer, Option, Context, Stream } from "effect" +import { Effect, Exit, Layer, Option, Context, Schema, Stream } from "effect" import { EffectBridge } from "@/effect" import { InstanceState } from "@/effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" @@ -47,16 +47,16 @@ export type Resource = z.infer export const ToolsChanged = BusEvent.define( "mcp.tools.changed", - z.object({ - server: z.string(), + Schema.Struct({ + server: Schema.String, }), ) export const BrowserOpenFailed = BusEvent.define( "mcp.browser.open.failed", - z.object({ - mcpName: z.string(), - url: z.string(), + Schema.Struct({ + mcpName: Schema.String, + url: Schema.String, }), ) diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 6943b3d93..05c832016 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -73,16 +73,14 @@ export class Approval extends Schema.Class("PermissionApproval")({ } export const Event = { - Asked: BusEvent.define("permission.asked", Request.zod), + Asked: BusEvent.define("permission.asked", Request), Replied: BusEvent.define( "permission.replied", - zod( - Schema.Struct({ - sessionID: SessionID, - requestID: PermissionID, - reply: Reply, - }), - ), + Schema.Struct({ + sessionID: SessionID, + requestID: PermissionID, + reply: Reply, + }), ), } diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index ab60cff7a..70a959064 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -53,7 +53,7 @@ export const Info = Schema.Struct({ export type Info = Types.DeepMutable> export const Event = { - Updated: BusEvent.define("project.updated", Info.zod), + Updated: BusEvent.define("project.updated", Info), } type Row = typeof ProjectTable.$inferSelect diff --git a/packages/opencode/src/project/vcs.ts b/packages/opencode/src/project/vcs.ts index ba028f7e8..e8c6ff2ac 100644 --- a/packages/opencode/src/project/vcs.ts +++ b/packages/opencode/src/project/vcs.ts @@ -1,4 +1,4 @@ -import { Effect, Layer, Context, Stream, Scope } from "effect" +import { Effect, Layer, Context, Schema, Stream, Scope } from "effect" import { formatPatch, structuredPatch } from "diff" import path from "path" import { Bus } from "@/bus" @@ -107,8 +107,8 @@ export type Mode = z.infer export const Event = { BranchUpdated: BusEvent.define( "vcs.branch.updated", - z.object({ - branch: z.string().optional(), + Schema.Struct({ + branch: Schema.optional(Schema.String), }), ), } diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 3d00de596..604fa77fb 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -3,13 +3,14 @@ import { Bus } from "@/bus" import { InstanceState } from "@/effect" import { Instance } from "@/project/instance" import type { Proc } from "#pty" -import z from "zod" import { Log } from "../util" import { lazy } from "@opencode-ai/shared/util/lazy" import { Shell } from "@/shell/shell" import { Plugin } from "@/plugin" import { PtyID } from "./schema" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema, Types } from "effect" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import { EffectBridge } from "@/effect" const log = Log.create({ service: "pty" }) @@ -53,47 +54,47 @@ const meta = (cursor: number) => { const pty = lazy(() => import("#pty")) -export const Info = z - .object({ - id: PtyID.zod, - title: z.string(), - command: z.string(), - args: z.array(z.string()), - cwd: z.string(), - status: z.enum(["running", "exited"]), - pid: z.number(), - }) - .meta({ ref: "Pty" }) - -export type Info = z.infer - -export const CreateInput = z.object({ - command: z.string().optional(), - args: z.array(z.string()).optional(), - cwd: z.string().optional(), - title: z.string().optional(), - env: z.record(z.string(), z.string()).optional(), +export const Info = Schema.Struct({ + id: PtyID, + title: Schema.String, + command: Schema.String, + args: Schema.Array(Schema.String), + cwd: Schema.String, + status: Schema.Literals(["running", "exited"]), + pid: Schema.Number, }) + .annotate({ identifier: "Pty" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) -export type CreateInput = z.infer +export type Info = Types.DeepMutable> -export const UpdateInput = z.object({ - title: z.string().optional(), - size: z - .object({ - rows: z.number(), - cols: z.number(), - }) - .optional(), -}) +export const CreateInput = Schema.Struct({ + command: Schema.optional(Schema.String), + args: Schema.optional(Schema.Array(Schema.String)), + cwd: Schema.optional(Schema.String), + title: Schema.optional(Schema.String), + env: Schema.optional(Schema.Record(Schema.String, Schema.String)), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) -export type UpdateInput = z.infer +export type CreateInput = Types.DeepMutable> + +export const UpdateInput = Schema.Struct({ + title: Schema.optional(Schema.String), + size: Schema.optional( + Schema.Struct({ + rows: Schema.Number, + cols: Schema.Number, + }), + ), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) + +export type UpdateInput = Types.DeepMutable> export const Event = { - Created: BusEvent.define("pty.created", z.object({ info: Info })), - Updated: BusEvent.define("pty.updated", z.object({ info: Info })), - Exited: BusEvent.define("pty.exited", z.object({ id: PtyID.zod, exitCode: z.number() })), - Deleted: BusEvent.define("pty.deleted", z.object({ id: PtyID.zod })), + Created: BusEvent.define("pty.created", Schema.Struct({ info: Info })), + Updated: BusEvent.define("pty.updated", Schema.Struct({ info: Info })), + Exited: BusEvent.define("pty.exited", Schema.Struct({ id: PtyID, exitCode: Schema.Number })), + Deleted: BusEvent.define("pty.deleted", Schema.Struct({ id: PtyID })), } export interface Interface { diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index 3b377c982..626c71826 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -94,9 +94,9 @@ class Rejected extends Schema.Class("QuestionRejected")({ }) {} export const Event = { - Asked: BusEvent.define("question.asked", Request.zod), - Replied: BusEvent.define("question.replied", zod(Replied)), - Rejected: BusEvent.define("question.rejected", zod(Rejected)), + Asked: BusEvent.define("question.asked", Request), + Replied: BusEvent.define("question.replied", Replied), + Rejected: BusEvent.define("question.rejected", Rejected), } export class RejectedError extends Schema.TaggedErrorClass()("QuestionRejectedError", {}) { @@ -194,7 +194,7 @@ export const layer = Layer.effect( yield* bus.publish(Event.Replied, { sessionID: existing.info.sessionID, requestID: existing.info.id, - answers: input.answers, + answers: input.answers.map((a) => [...a]), }) yield* Deferred.succeed(existing.deferred, input.answers) }) diff --git a/packages/opencode/src/server/event.ts b/packages/opencode/src/server/event.ts index 49325b2bb..d5f10f47d 100644 --- a/packages/opencode/src/server/event.ts +++ b/packages/opencode/src/server/event.ts @@ -1,7 +1,7 @@ import { BusEvent } from "@/bus/bus-event" -import z from "zod" +import { Schema } from "effect" export const Event = { - Connected: BusEvent.define("server.connected", z.object({})), - Disposed: BusEvent.define("global.disposed", z.object({})), + Connected: BusEvent.define("server.connected", Schema.Struct({})), + Disposed: BusEvent.define("global.disposed", Schema.Struct({})), } diff --git a/packages/opencode/src/server/routes/control/workspace.ts b/packages/opencode/src/server/routes/control/workspace.ts index 9ff747b68..d48c687f3 100644 --- a/packages/opencode/src/server/routes/control/workspace.ts +++ b/packages/opencode/src/server/routes/control/workspace.ts @@ -3,6 +3,7 @@ import { describeRoute, resolver, validator } from "hono-openapi" import z from "zod" import { listAdaptors } from "@/control-plane/adaptors" import { Workspace } from "@/control-plane/workspace" +import { zodObject } from "@/util/effect-zod" import { Instance } from "@/project/instance" import { errors } from "../../error" import { lazy } from "@/util/lazy" @@ -107,7 +108,7 @@ export const WorkspaceRoutes = lazy(() => description: "Workspace status", content: { "application/json": { - schema: resolver(z.array(Workspace.ConnectionStatus)), + schema: resolver(z.array(zodObject(Workspace.ConnectionStatus))), }, }, }, diff --git a/packages/opencode/src/server/routes/global.ts b/packages/opencode/src/server/routes/global.ts index 54f9972e0..a1199a469 100644 --- a/packages/opencode/src/server/routes/global.ts +++ b/packages/opencode/src/server/routes/global.ts @@ -1,7 +1,7 @@ import { Hono, type Context } from "hono" import { describeRoute, resolver, validator } from "hono-openapi" import { streamSSE } from "hono/streaming" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import z from "zod" import { BusEvent } from "@/bus/bus-event" import { SyncEvent } from "@/sync" @@ -18,7 +18,7 @@ import { errors } from "../error" const log = Log.create({ service: "server" }) -export const GlobalDisposedEvent = BusEvent.define("global.disposed", z.object({})) +export const GlobalDisposedEvent = BusEvent.define("global.disposed", Schema.Struct({})) async function streamEvents(c: Context, subscribe: (q: AsyncQueue) => () => void) { return streamSSE(c, async (stream) => { diff --git a/packages/opencode/src/server/routes/instance/pty.ts b/packages/opencode/src/server/routes/instance/pty.ts index a25b66e9f..51c469924 100644 --- a/packages/opencode/src/server/routes/instance/pty.ts +++ b/packages/opencode/src/server/routes/instance/pty.ts @@ -23,7 +23,7 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { description: "List of sessions", content: { "application/json": { - schema: resolver(Pty.Info.array()), + schema: resolver(Pty.Info.zod.array()), }, }, }, @@ -46,18 +46,18 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { description: "Created session", content: { "application/json": { - schema: resolver(Pty.Info), + schema: resolver(Pty.Info.zod), }, }, }, ...errors(400), }, }), - validator("json", Pty.CreateInput), + validator("json", Pty.CreateInput.zod), async (c) => jsonRequest("PtyRoutes.create", c, function* () { const pty = yield* Pty.Service - return yield* pty.create(c.req.valid("json")) + return yield* pty.create(c.req.valid("json") as Pty.CreateInput) }), ) .get( @@ -71,7 +71,7 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { description: "Session info", content: { "application/json": { - schema: resolver(Pty.Info), + schema: resolver(Pty.Info.zod), }, }, }, @@ -105,7 +105,7 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { description: "Updated session", content: { "application/json": { - schema: resolver(Pty.Info), + schema: resolver(Pty.Info.zod), }, }, }, @@ -113,11 +113,11 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { }, }), validator("param", z.object({ ptyID: PtyID.zod })), - validator("json", Pty.UpdateInput), + validator("json", Pty.UpdateInput.zod), async (c) => jsonRequest("PtyRoutes.update", c, function* () { const pty = yield* Pty.Service - return yield* pty.update(c.req.valid("param").ptyID, c.req.valid("json")) + return yield* pty.update(c.req.valid("param").ptyID, c.req.valid("json") as Pty.UpdateInput) }), ) .delete( diff --git a/packages/opencode/src/server/routes/instance/tui.ts b/packages/opencode/src/server/routes/instance/tui.ts index d6add67b9..a610590e8 100644 --- a/packages/opencode/src/server/routes/instance/tui.ts +++ b/packages/opencode/src/server/routes/instance/tui.ts @@ -1,9 +1,12 @@ import { Hono, type Context } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" +import { Schema } from "effect" import z from "zod" import { Bus } from "@/bus" import { Session } from "@/session" +import type { SessionID } from "@/session/schema" import { TuiEvent } from "@/cli/cmd/tui/event" +import { zodObject } from "@/util/effect-zod" import { AsyncQueue } from "@/util/queue" import { errors } from "../../error" import { lazy } from "@/util/lazy" @@ -96,9 +99,9 @@ export const TuiRoutes = lazy(() => ...errors(400), }, }), - validator("json", TuiEvent.PromptAppend.properties), + validator("json", zodObject(TuiEvent.PromptAppend.properties)), async (c) => { - await Bus.publish(TuiEvent.PromptAppend, c.req.valid("json")) + await Bus.publish(TuiEvent.PromptAppend, c.req.valid("json") as { text: string }) return c.json(true) }, ) @@ -305,9 +308,9 @@ export const TuiRoutes = lazy(() => }, }, }), - validator("json", TuiEvent.ToastShow.properties), + validator("json", zodObject(TuiEvent.ToastShow.properties)), async (c) => { - await Bus.publish(TuiEvent.ToastShow, c.req.valid("json")) + await Bus.publish(TuiEvent.ToastShow, c.req.valid("json") as Schema.Schema.Type) return c.json(true) }, ) @@ -336,7 +339,7 @@ export const TuiRoutes = lazy(() => return z .object({ type: z.literal(def.type), - properties: def.properties, + properties: zodObject(def.properties), }) .meta({ ref: `Event.${def.type}`, @@ -345,8 +348,9 @@ export const TuiRoutes = lazy(() => ), ), async (c) => { - const evt = c.req.valid("json") - await Bus.publish(Object.values(TuiEvent).find((def) => def.type === evt.type)!, evt.properties) + const evt = c.req.valid("json") as { type: string; properties: Record } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await Bus.publish(Object.values(TuiEvent).find((def) => def.type === evt.type)! as any, evt.properties as any) return c.json(true) }, ) @@ -368,9 +372,9 @@ export const TuiRoutes = lazy(() => ...errors(400, 404), }, }), - validator("json", TuiEvent.SessionSelect.properties), + validator("json", zodObject(TuiEvent.SessionSelect.properties)), async (c) => { - const { sessionID } = c.req.valid("json") + const { sessionID } = c.req.valid("json") as { sessionID: SessionID } await runRequest( "TuiRoutes.sessionSelect", c, diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index defdb870d..dc126e683 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -13,7 +13,7 @@ import { Plugin } from "@/plugin" import { Config } from "@/config" import { NotFoundError } from "@/storage" import { ModelID, ProviderID } from "@/provider/schema" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema } from "effect" import { InstanceState } from "@/effect" import { isOverflow as overflow, usable } from "./overflow" import { makeRuntime } from "@/effect/run-service" @@ -24,8 +24,8 @@ const log = Log.create({ service: "session.compaction" }) export const Event = { Compacted: BusEvent.define( "session.compacted", - z.object({ - sessionID: SessionID.zod, + Schema.Struct({ + sessionID: SessionID, }), ), } diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index c5a35c7b4..d04645b73 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -617,12 +617,12 @@ export const Event = { }), PartDelta: BusEvent.define( "message.part.delta", - z.object({ - sessionID: SessionID.zod, - messageID: MessageID.zod, - partID: PartID.zod, - field: z.string(), - delta: z.string(), + Schema.Struct({ + sessionID: SessionID, + messageID: MessageID, + partID: PartID, + field: Schema.String, + delta: Schema.String, }), ), PartRemoved: SyncEvent.define({ diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 1e046fdf7..f4fe3bf8b 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -273,17 +273,18 @@ export const Event = { }), Diff: BusEvent.define( "session.diff", - z.object({ - sessionID: SessionID.zod, - diff: Snapshot.FileDiff.zod.array(), + Schema.Struct({ + sessionID: SessionID, + diff: Schema.Array(Snapshot.FileDiff), }), ), Error: BusEvent.define( "session.error", - z.object({ - sessionID: SessionID.zod.optional(), - // z.lazy defers access to break circular dep: session → message-v2 → provider → plugin → session - error: z.lazy(() => (MessageV2.Assistant.zod as unknown as z.ZodObject).shape.error), + Schema.Struct({ + sessionID: Schema.optional(SessionID), + // Reuses MessageV2.Assistant.fields.error (already Schema.optional) so + // the derived zod keeps the same discriminated-union shape on the bus. + error: MessageV2.Assistant.fields.error, }), ), } diff --git a/packages/opencode/src/session/status.ts b/packages/opencode/src/session/status.ts index b9b9fd7e7..e5165a787 100644 --- a/packages/opencode/src/session/status.ts +++ b/packages/opencode/src/session/status.ts @@ -28,16 +28,16 @@ export type Info = Schema.Schema.Type export const Event = { Status: BusEvent.define( "session.status", - z.object({ - sessionID: SessionID.zod, - status: Info.zod, + Schema.Struct({ + sessionID: SessionID, + status: Info, }), ), // deprecated Idle: BusEvent.define( "session.idle", - z.object({ - sessionID: SessionID.zod, + Schema.Struct({ + sessionID: SessionID, }), ), } diff --git a/packages/opencode/src/session/todo.ts b/packages/opencode/src/session/todo.ts index 257b586ed..c3a9b106b 100644 --- a/packages/opencode/src/session/todo.ts +++ b/packages/opencode/src/session/todo.ts @@ -22,9 +22,9 @@ export type Info = Schema.Schema.Type export const Event = { Updated: BusEvent.define( "todo.updated", - z.object({ - sessionID: SessionID.zod, - todos: z.array(Info.zod), + Schema.Struct({ + sessionID: SessionID, + todos: Schema.Array(Info), }), ), } diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index 482ad4fbb..35a5abd0b 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -8,51 +8,48 @@ import { EventSequenceTable, EventTable } from "./event.sql" import { WorkspaceContext } from "@/control-plane/workspace-context" import { EventID } from "./schema" import { Flag } from "@/flag/flag" -import { Schema as EffectSchema, Types } from "effect" +import { Schema as EffectSchema } from "effect" import { zodObject } from "@/util/effect-zod" -import { isRecord } from "@/util/record" +import type { DeepMutable } from "@/util/schema" + +// Keep `Event["data"]` mutable because projectors mutate the persisted shape +// when writing to the database. Bus payloads (`Properties`) stay readonly — +// subscribers only read. export type Definition< + Type extends string = string, Schema extends EffectSchema.Top = EffectSchema.Top, BusSchema extends EffectSchema.Top = Schema, > = { - type: string + type: Type version: number aggregate: string - effectSchema: Schema - effectProperties: BusSchema - schema: z.ZodObject - - // This is temporary and only exists for compatibility with bus - // event definitions - properties: z.ZodObject + schema: Schema + // Bus event payload schema. Defaults to `schema` unless `busSchema` was + // passed at definition time (see `session.updated`, whose projector + // expands the persisted data to a `{ sessionID, info }` bus payload). + properties: BusSchema } export type Event = { id: string seq: number aggregateID: string - data: Types.DeepMutable> + data: DeepMutable> } -export type Properties = Types.DeepMutable< - EffectSchema.Schema.Type -> +export type Properties = EffectSchema.Schema.Type export type SerializedEvent = Event & { type: string } type ProjectorFunc = (db: Database.TxOrDb, data: unknown) => void +type ConvertEvent = (type: string, data: Event["data"]) => unknown | Promise export const registry = new Map() let projectors: Map | undefined const versions = new Map() let frozen = false -let convertEvent: (type: string, event: Event["data"]) => Promise | unknown - -function asRecord(input: unknown) { - if (isRecord(input)) return input - throw new Error(`SyncEvent.convertEvent must return an object, got: ${JSON.stringify(input)}`) -} +let convertEvent: ConvertEvent export function reset() { frozen = false @@ -60,7 +57,7 @@ export function reset() { convertEvent = (_, data) => data } -export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; convertEvent?: typeof convertEvent }) { +export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; convertEvent?: ConvertEvent }) { projectors = new Map(input.projectors) // Install all the latest event defs to the bus. We only ever emit @@ -76,7 +73,7 @@ export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; co // Freeze the system so it clearly errors if events are defined // after `init` which would cause bugs frozen = true - convertEvent = input.convertEvent || ((_, data) => data) + convertEvent = input.convertEvent ?? ((_, data) => data) } export function versionedType(type: A): A @@ -96,21 +93,17 @@ export function define< aggregate: Agg schema: Schema busSchema?: BusSchema -}): Definition { +}): Definition { if (frozen) { throw new Error("Error defining sync event: sync system has been frozen") } - const effectProperties = (input.busSchema ?? input.schema) as BusSchema - const def = { type: input.type, version: input.version, aggregate: input.aggregate, - effectSchema: input.schema, - effectProperties, - schema: zodObject(input.schema), - properties: zodObject(effectProperties), + schema: input.schema, + properties: (input.busSchema ?? input.schema) as BusSchema, } versions.set(def.type, Math.max(def.version, versions.get(def.type) || 0)) @@ -167,12 +160,11 @@ function process(def: Def, event: Event, options: { Database.effect(() => { if (options?.publish) { const result = convertEvent(def.type, event.data) + const publish = (data: unknown) => ProjectBus.publish(def, data as Properties) if (result instanceof Promise) { - void result.then((data) => { - void ProjectBus.publish({ type: def.type, properties: def.properties }, asRecord(data)) - }) + void result.then(publish) } else { - void ProjectBus.publish({ type: def.type, properties: def.properties }, asRecord(result)) + void publish(result) } GlobalBus.emit("event", { @@ -292,7 +284,7 @@ export function payloads() { id: z.string(), seq: z.number(), aggregateID: z.literal(def.aggregate), - data: def.schema, + data: zodObject(def.schema), }) .meta({ ref: `SyncEvent.${def.type}`, diff --git a/packages/opencode/src/util/effect-zod.ts b/packages/opencode/src/util/effect-zod.ts index edbbf4d54..24ff9a10e 100644 --- a/packages/opencode/src/util/effect-zod.ts +++ b/packages/opencode/src/util/effect-zod.ts @@ -59,8 +59,17 @@ function walk(ast: SchemaAST.AST): z.ZodTypeAny { function walkUncached(ast: SchemaAST.AST): z.ZodTypeAny { const override = (ast.annotations as any)?.[ZodOverride] as z.ZodTypeAny | undefined - if (override) return override + // `description` annotations layer on top of an override so callers can + // reuse a shared override schema (e.g. `SessionID`) and still add a + // per-field description on the outer wrapper. + const base = override ?? bodyWithChecks(ast) + const desc = SchemaAST.resolveDescription(ast) + const ref = SchemaAST.resolveIdentifier(ast) + const described = desc ? base.describe(desc) : base + return ref ? described.meta({ ref }) : described +} +function bodyWithChecks(ast: SchemaAST.AST): z.ZodTypeAny { // Schema.Class wraps its fields in a Declaration AST plus an encoding that // constructs the class instance. For the Zod derivation we want the plain // field shape (the decoded/consumer view), not the class instance — so @@ -74,11 +83,7 @@ function walkUncached(ast: SchemaAST.AST): z.ZodTypeAny { const hasEncoding = ast.encoding?.length && ast._tag !== "Declaration" const hasTransform = hasEncoding && !(SchemaAST.isOptional(ast) && extractDefault(ast) !== undefined) const base = hasTransform ? encoded(ast) : body(ast) - const checked = ast.checks?.length ? applyChecks(base, ast.checks, ast) : base - const desc = SchemaAST.resolveDescription(ast) - const ref = SchemaAST.resolveIdentifier(ast) - const described = desc ? checked.describe(desc) : checked - return ref ? described.meta({ ref }) : described + return ast.checks?.length ? applyChecks(base, ast.checks, ast) : base } // Walk the encoded side and apply each link's decode to produce the decoded diff --git a/packages/opencode/src/util/schema.ts b/packages/opencode/src/util/schema.ts index 7f94ee5d1..0c50482bb 100644 --- a/packages/opencode/src/util/schema.ts +++ b/packages/opencode/src/util/schema.ts @@ -10,6 +10,34 @@ export const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0)) */ export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)) +/** + * Strip `readonly` from a nested type. Stand-in for `effect`'s `Types.DeepMutable` + * until `effect:core/x228my` ("Types.DeepMutable widens unknown to `{}`") lands. + * + * The upstream version falls through `unknown` into `{ -readonly [K in keyof T]: ... }` + * where `keyof unknown = never`, so `unknown` collapses to `{}`. This local + * version gates the object branch on `extends object` (which `unknown` does + * not) so `unknown` passes through untouched. + * + * Primitive bailout matches upstream — without it, branded strings like + * `string & Brand<"SessionID">` fall into the object branch and get their + * prototype methods walked. + * + * Tuple branch preserves readonly tuples (e.g. `ConfigPlugin.Spec`'s + * `readonly [string, Options]`); the general array branch would otherwise + * widen them to unbounded arrays. + */ +// eslint-disable-next-line @typescript-eslint/ban-types +export type DeepMutable = T extends string | number | boolean | bigint | symbol | Function + ? T + : T extends readonly [unknown, ...unknown[]] + ? { -readonly [K in keyof T]: DeepMutable } + : T extends readonly (infer U)[] + ? DeepMutable[] + : T extends object + ? { -readonly [K in keyof T]: DeepMutable } + : T + /** * Attach static methods to a schema object. Designed to be used with `.pipe()`: * @@ -26,13 +54,16 @@ export const withStatics = (schema: S): S & M => Object.assign(schema, methods(schema)) -declare const NewtypeBrand: unique symbol -type NewtypeBrand = { readonly [NewtypeBrand]: Tag } - /** * Nominal wrapper for scalar types. The class itself is a valid schema — * pass it directly to `Schema.decode`, `Schema.decodeEffect`, etc. * + * Overrides `~type.make` on the derived `Schema.Opaque` so `Schema.Schema.Type` + * of a field using this newtype resolves to `Self` rather than the underlying + * branded phantom. Without that override, passing a class instance to code + * typed against `Schema.Schema.Type` would require a cast even + * though the values are structurally equivalent at runtime. + * * @example * class QuestionID extends Newtype()("QuestionID", Schema.String) { * static make(id: string): QuestionID { @@ -44,10 +75,8 @@ type NewtypeBrand = { readonly [NewtypeBrand]: Tag } */ export function Newtype() { return (tag: Tag, schema: S) => { - type Branded = NewtypeBrand - abstract class Base { - declare readonly [NewtypeBrand]: Tag + declare readonly _newtype: Tag static make(value: Schema.Schema.Type): Self { return value as unknown as Self @@ -56,8 +85,10 @@ export function Newtype() { Object.setPrototypeOf(Base, schema) - return Base as unknown as (abstract new (_: never) => Branded) & { + return Base as unknown as (abstract new (_: never) => { readonly _newtype: Tag }) & { readonly make: (value: Schema.Schema.Type) => Self - } & Omit, "make"> + } & Omit, "make" | "~type.make"> & { + readonly "~type.make": Self + } } } diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index bbebeaa49..e122fe453 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -13,7 +13,7 @@ import { errorMessage } from "../util/error" import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" import { Git } from "@/git" -import { Effect, Layer, Path, Scope, Context, Stream } from "effect" +import { Effect, Layer, Path, Schema, Scope, Context, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/shared/filesystem" @@ -26,15 +26,15 @@ const log = Log.create({ service: "worktree" }) export const Event = { Ready: BusEvent.define( "worktree.ready", - z.object({ - name: z.string(), - branch: z.string(), + Schema.Struct({ + name: Schema.String, + branch: Schema.String, }), ), Failed: BusEvent.define( "worktree.failed", - z.object({ - message: z.string(), + Schema.Struct({ + message: Schema.String, }), ), } diff --git a/packages/opencode/test/bus/bus-effect.test.ts b/packages/opencode/test/bus/bus-effect.test.ts index 6f96a89c8..3d602ae6f 100644 --- a/packages/opencode/test/bus/bus-effect.test.ts +++ b/packages/opencode/test/bus/bus-effect.test.ts @@ -1,6 +1,5 @@ import { describe, expect } from "bun:test" -import { Deferred, Effect, Layer, Stream } from "effect" -import z from "zod" +import { Deferred, Effect, Layer, Schema, Stream } from "effect" import { Bus } from "../../src/bus" import { BusEvent } from "../../src/bus/bus-event" import { Instance } from "../../src/project/instance" @@ -9,8 +8,8 @@ import { provideInstance, provideTmpdirInstance, tmpdirScoped } from "../fixture import { testEffect } from "../lib/effect" const TestEvent = { - Ping: BusEvent.define("test.effect.ping", z.object({ value: z.number() })), - Pong: BusEvent.define("test.effect.pong", z.object({ message: z.string() })), + Ping: BusEvent.define("test.effect.ping", Schema.Struct({ value: Schema.Number })), + Pong: BusEvent.define("test.effect.pong", Schema.Struct({ message: Schema.String })), } const node = CrossSpawnSpawner.defaultLayer diff --git a/packages/opencode/test/bus/bus-integration.test.ts b/packages/opencode/test/bus/bus-integration.test.ts index e42bd5299..280834457 100644 --- a/packages/opencode/test/bus/bus-integration.test.ts +++ b/packages/opencode/test/bus/bus-integration.test.ts @@ -1,11 +1,11 @@ import { afterEach, describe, expect, test } from "bun:test" -import z from "zod" +import { Schema } from "effect" import { Bus } from "../../src/bus" import { BusEvent } from "../../src/bus/bus-event" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" -const TestEvent = BusEvent.define("test.integration", z.object({ value: z.number() })) +const TestEvent = BusEvent.define("test.integration", Schema.Struct({ value: Schema.Number })) function withInstance(directory: string, fn: () => Promise) { return Instance.provide({ directory, fn }) @@ -42,7 +42,7 @@ describe("Bus integration: acquireRelease subscriber pattern", () => { await using tmp = await tmpdir() const received: Array<{ type: string; value?: number }> = [] - const OtherEvent = BusEvent.define("test.other", z.object({ value: z.number() })) + const OtherEvent = BusEvent.define("test.other", Schema.Struct({ value: Schema.Number })) await withInstance(tmp.path, async () => { Bus.subscribeAll((evt) => { diff --git a/packages/opencode/test/bus/bus.test.ts b/packages/opencode/test/bus/bus.test.ts index 3df179787..cdacdd517 100644 --- a/packages/opencode/test/bus/bus.test.ts +++ b/packages/opencode/test/bus/bus.test.ts @@ -1,13 +1,13 @@ import { afterEach, describe, expect, test } from "bun:test" -import z from "zod" +import { Schema } from "effect" import { Bus } from "../../src/bus" import { BusEvent } from "../../src/bus/bus-event" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" const TestEvent = { - Ping: BusEvent.define("test.ping", z.object({ value: z.number() })), - Pong: BusEvent.define("test.pong", z.object({ message: z.string() })), + Ping: BusEvent.define("test.ping", Schema.Struct({ value: Schema.Number })), + Pong: BusEvent.define("test.pong", Schema.Struct({ message: Schema.String })), } function withInstance(directory: string, fn: () => Promise) { diff --git a/packages/opencode/test/session/session.test.ts b/packages/opencode/test/session/session.test.ts index f63ad9bee..d4a1d711d 100644 --- a/packages/opencode/test/session/session.test.ts +++ b/packages/opencode/test/session/session.test.ts @@ -111,9 +111,12 @@ describe("step-finish token propagation via Bus event", () => { mode: "", } as unknown as MessageV2.Info) + // Bus subscribers receive readonly Schema.Type payloads; `MessageV2.Part` + // is the mutable domain type. Cast bridges the two — safe because the + // test only reads the value afterwards. let received: MessageV2.Part | undefined const unsub = Bus.subscribe(MessageV2.Event.PartUpdated, (event) => { - received = event.properties.part + received = event.properties.part as MessageV2.Part }) const tokens = { From 24892559ae10e8df5bde8211b7e4c7a6851f3de6 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 19:38:54 +0000 Subject: [PATCH 091/426] chore: generate --- .../opencode/src/server/routes/instance/tui.ts | 5 ++++- packages/sdk/openapi.json | 18 ++---------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/tui.ts b/packages/opencode/src/server/routes/instance/tui.ts index a610590e8..932cf509e 100644 --- a/packages/opencode/src/server/routes/instance/tui.ts +++ b/packages/opencode/src/server/routes/instance/tui.ts @@ -310,7 +310,10 @@ export const TuiRoutes = lazy(() => }), validator("json", zodObject(TuiEvent.ToastShow.properties)), async (c) => { - await Bus.publish(TuiEvent.ToastShow, c.req.valid("json") as Schema.Schema.Type) + await Bus.publish( + TuiEvent.ToastShow, + c.req.valid("json") as Schema.Schema.Type, + ) return c.json(true) }, ) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index e54648e19..0c3d18972 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -6804,7 +6804,6 @@ }, "duration": { "description": "Duration in milliseconds", - "default": 5000, "type": "number" } }, @@ -7638,20 +7637,8 @@ "type": "string" }, "event": { - "anyOf": [ - { - "type": "string", - "const": "add" - }, - { - "type": "string", - "const": "change" - }, - { - "type": "string", - "const": "unlink" - } - ] + "type": "string", + "enum": ["add", "change", "unlink"] } }, "required": ["file", "event"] @@ -8507,7 +8494,6 @@ }, "duration": { "description": "Duration in milliseconds", - "default": 5000, "type": "number" } }, From 3910a6e527c87f514c479a1c1c3d6d5f4d1aa315 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 16:09:34 -0400 Subject: [PATCH 092/426] refactor(tool): migrate tool framework + all 18 built-in tools to Effect Schema (#23244) --- .../server/routes/instance/experimental.ts | 3 +- packages/opencode/src/session/prompt.ts | 3 +- packages/opencode/src/tool/apply_patch.ts | 13 +- packages/opencode/src/tool/bash.ts | 26 +- packages/opencode/src/tool/codesearch.ts | 35 +- packages/opencode/src/tool/edit.ts | 19 +- packages/opencode/src/tool/glob.ts | 20 +- packages/opencode/src/tool/grep.ts | 18 +- packages/opencode/src/tool/invalid.ts | 13 +- packages/opencode/src/tool/lsp.ts | 21 +- packages/opencode/src/tool/plan.ts | 7 +- packages/opencode/src/tool/question.ts | 13 +- packages/opencode/src/tool/read.ts | 26 +- packages/opencode/src/tool/registry.ts | 12 +- packages/opencode/src/tool/skill.ts | 9 +- packages/opencode/src/tool/task.ts | 27 +- packages/opencode/src/tool/todo.ts | 37 +- packages/opencode/src/tool/tool.ts | 55 +- packages/opencode/src/tool/webfetch.ts | 22 +- packages/opencode/src/tool/websearch.ts | 35 +- packages/opencode/src/tool/write.ts | 14 +- packages/opencode/src/util/effect-zod.ts | 11 + .../__snapshots__/parameters.test.ts.snap | 495 ++++++++++++++++++ .../opencode/test/tool/parameters.test.ts | 260 +++++++++ .../opencode/test/tool/tool-define.test.ts | 46 +- 25 files changed, 1035 insertions(+), 205 deletions(-) create mode 100644 packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap create mode 100644 packages/opencode/test/tool/parameters.test.ts diff --git a/packages/opencode/src/server/routes/instance/experimental.ts b/packages/opencode/src/server/routes/instance/experimental.ts index 93a5d98c9..f13003cb4 100644 --- a/packages/opencode/src/server/routes/instance/experimental.ts +++ b/packages/opencode/src/server/routes/instance/experimental.ts @@ -1,6 +1,7 @@ import { Hono } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" import z from "zod" +import * as EffectZod from "@/util/effect-zod" import { ProviderID, ModelID } from "@/provider/schema" import { ToolRegistry } from "@/tool" import { Worktree } from "@/worktree" @@ -213,7 +214,7 @@ export const ExperimentalRoutes = lazy(() => tools.map((t) => ({ id: t.id, description: t.description, - parameters: z.toJSONSchema(t.parameters), + parameters: EffectZod.toJsonSchema(t.parameters), })), ) }, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 0f48eb64e..5f3530bce 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1,6 +1,7 @@ import path from "path" import os from "os" import z from "zod" +import * as EffectZod from "@/util/effect-zod" import { SessionID, MessageID, PartID } from "./schema" import { MessageV2 } from "./message-v2" import { Log } from "../util" @@ -405,7 +406,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the providerID: input.model.providerID, agent: input.agent, })) { - const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters)) + const schema = ProviderTransform.schema(input.model, EffectZod.toJsonSchema(item.parameters)) tools[item.id] = tool({ description: item.description, inputSchema: jsonSchema(schema), diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts index 33112c43c..effc428c7 100644 --- a/packages/opencode/src/tool/apply_patch.ts +++ b/packages/opencode/src/tool/apply_patch.ts @@ -1,6 +1,5 @@ -import z from "zod" import * as path from "path" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import { Bus } from "../bus" import { FileWatcher } from "../file/watcher" @@ -16,8 +15,8 @@ import { File } from "../file" import { Format } from "../format" import * as Bom from "@/util/bom" -const PatchParams = z.object({ - patchText: z.string().describe("The full patch text that describes all changes to be made"), +export const Parameters = Schema.Struct({ + patchText: Schema.String.annotate({ description: "The full patch text that describes all changes to be made" }), }) export const ApplyPatchTool = Tool.define( @@ -28,7 +27,7 @@ export const ApplyPatchTool = Tool.define( const format = yield* Format.Service const bus = yield* Bus.Service - const run = Effect.fn("ApplyPatchTool.execute")(function* (params: z.infer, ctx: Tool.Context) { + const run = Effect.fn("ApplyPatchTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { if (!params.patchText) { return yield* Effect.fail(new Error("patchText is required")) } @@ -297,8 +296,8 @@ export const ApplyPatchTool = Tool.define( return { description: DESCRIPTION, - parameters: PatchParams, - execute: (params: z.infer, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 6260b2221..2d4d59b1b 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -1,4 +1,4 @@ -import z from "zod" +import { Schema } from "effect" import os from "os" import { createWriteStream } from "node:fs" import * as Tool from "./tool" @@ -50,20 +50,16 @@ const FILES = new Set([ const FLAGS = new Set(["-destination", "-literalpath", "-path"]) const SWITCHES = new Set(["-confirm", "-debug", "-force", "-nonewline", "-recurse", "-verbose", "-whatif"]) -const Parameters = z.object({ - command: z.string().describe("The command to execute"), - timeout: z.number().describe("Optional timeout in milliseconds").optional(), - workdir: z - .string() - .describe( - `The working directory to run the command in. Defaults to the current directory. Use this instead of 'cd' commands.`, - ) - .optional(), - description: z - .string() - .describe( +export const Parameters = Schema.Struct({ + command: Schema.String.annotate({ description: "The command to execute" }), + timeout: Schema.optional(Schema.Number).annotate({ description: "Optional timeout in milliseconds" }), + workdir: Schema.optional(Schema.String).annotate({ + description: `The working directory to run the command in. Defaults to the current directory. Use this instead of 'cd' commands.`, + }), + description: Schema.String.annotate({ + description: "Clear, concise description of what this command does in 5-10 words. Examples:\nInput: ls\nOutput: Lists files in current directory\n\nInput: git status\nOutput: Shows working tree status\n\nInput: npm install\nOutput: Installs package dependencies\n\nInput: mkdir foo\nOutput: Creates directory 'foo'", - ), + }), }) type Part = { @@ -587,7 +583,7 @@ export const BashTool = Tool.define( .replaceAll("${maxLines}", String(Truncate.MAX_LINES)) .replaceAll("${maxBytes}", String(Truncate.MAX_BYTES)), parameters: Parameters, - execute: (params: z.infer, ctx: Tool.Context) => + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { const cwd = params.workdir ? yield* resolvePath(params.workdir, Instance.directory, shell) diff --git a/packages/opencode/src/tool/codesearch.ts b/packages/opencode/src/tool/codesearch.ts index ac9961e25..e10d21175 100644 --- a/packages/opencode/src/tool/codesearch.ts +++ b/packages/opencode/src/tool/codesearch.ts @@ -1,10 +1,23 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import { HttpClient } from "effect/unstable/http" import * as Tool from "./tool" import * as McpExa from "./mcp-exa" import DESCRIPTION from "./codesearch.txt" +export const Parameters = Schema.Struct({ + query: Schema.String.annotate({ + description: + "Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'", + }), + tokensNum: Schema.Number.check(Schema.isGreaterThanOrEqualTo(1000)) + .check(Schema.isLessThanOrEqualTo(50000)) + .pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(5000))) + .annotate({ + description: + "Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.", + }), +}) + export const CodeSearchTool = Tool.define( "codesearch", Effect.gen(function* () { @@ -12,21 +25,7 @@ export const CodeSearchTool = Tool.define( return { description: DESCRIPTION, - parameters: z.object({ - query: z - .string() - .describe( - "Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'", - ), - tokensNum: z - .number() - .min(1000) - .max(50000) - .default(5000) - .describe( - "Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.", - ), - }), + parameters: Parameters, execute: (params: { query: string; tokensNum: number }, ctx: Tool.Context) => Effect.gen(function* () { yield* ctx.ask({ @@ -45,7 +44,7 @@ export const CodeSearchTool = Tool.define( McpExa.CodeArgs, { query: params.query, - tokensNum: params.tokensNum || 5000, + tokensNum: params.tokensNum, }, "30 seconds", ) diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 35dd85b47..cfff5a0a3 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -3,9 +3,8 @@ // https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/utils/editCorrector.ts // https://github.com/cline/cline/blob/main/evals/diff-edits/diff-apply/diff-06-26-25.ts -import z from "zod" import * as path from "path" -import { Effect, Semaphore } from "effect" +import { Effect, Schema, Semaphore } from "effect" import * as Tool from "./tool" import { LSP } from "../lsp" import { createTwoFilesPatch, diffLines } from "diff" @@ -45,11 +44,15 @@ function lock(filePath: string) { return next } -const Parameters = z.object({ - filePath: z.string().describe("The absolute path to the file to modify"), - oldString: z.string().describe("The text to replace"), - newString: z.string().describe("The text to replace it with (must be different from oldString)"), - replaceAll: z.boolean().optional().describe("Replace all occurrences of oldString (default false)"), +export const Parameters = Schema.Struct({ + filePath: Schema.String.annotate({ description: "The absolute path to the file to modify" }), + oldString: Schema.String.annotate({ description: "The text to replace" }), + newString: Schema.String.annotate({ + description: "The text to replace it with (must be different from oldString)", + }), + replaceAll: Schema.optional(Schema.Boolean).annotate({ + description: "Replace all occurrences of oldString (default false)", + }), }) export const EditTool = Tool.define( @@ -63,7 +66,7 @@ export const EditTool = Tool.define( return { description: DESCRIPTION, parameters: Parameters, - execute: (params: z.infer, ctx: Tool.Context) => + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { if (!params.filePath) { throw new Error("filePath is required") diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts index 673bb9cc8..aeecfecb7 100644 --- a/packages/opencode/src/tool/glob.ts +++ b/packages/opencode/src/tool/glob.ts @@ -1,6 +1,5 @@ import path from "path" -import z from "zod" -import { Effect, Option } from "effect" +import { Effect, Option, Schema } from "effect" import * as Stream from "effect/Stream" import { InstanceState } from "@/effect" import { AppFileSystem } from "@opencode-ai/shared/filesystem" @@ -9,6 +8,13 @@ import { assertExternalDirectoryEffect } from "./external-directory" import DESCRIPTION from "./glob.txt" import * as Tool from "./tool" +export const Parameters = Schema.Struct({ + pattern: Schema.String.annotate({ description: "The glob pattern to match files against" }), + path: Schema.optional(Schema.String).annotate({ + description: `The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.`, + }), +}) + export const GlobTool = Tool.define( "glob", Effect.gen(function* () { @@ -17,15 +23,7 @@ export const GlobTool = Tool.define( return { description: DESCRIPTION, - parameters: z.object({ - pattern: z.string().describe("The glob pattern to match files against"), - path: z - .string() - .optional() - .describe( - `The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.`, - ), - }), + parameters: Parameters, execute: (params: { pattern: string; path?: string }, ctx: Tool.Context) => Effect.gen(function* () { const ins = yield* InstanceState.context diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts index caa75edad..416005431 100644 --- a/packages/opencode/src/tool/grep.ts +++ b/packages/opencode/src/tool/grep.ts @@ -1,5 +1,5 @@ import path from "path" -import z from "zod" +import { Schema } from "effect" import { Effect, Option } from "effect" import { InstanceState } from "@/effect" import { AppFileSystem } from "@opencode-ai/shared/filesystem" @@ -10,6 +10,16 @@ import * as Tool from "./tool" const MAX_LINE_LENGTH = 2000 +export const Parameters = Schema.Struct({ + pattern: Schema.String.annotate({ description: "The regex pattern to search for in file contents" }), + path: Schema.optional(Schema.String).annotate({ + description: "The directory to search in. Defaults to the current working directory.", + }), + include: Schema.optional(Schema.String).annotate({ + description: 'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")', + }), +}) + export const GrepTool = Tool.define( "grep", Effect.gen(function* () { @@ -18,11 +28,7 @@ export const GrepTool = Tool.define( return { description: DESCRIPTION, - parameters: z.object({ - pattern: z.string().describe("The regex pattern to search for in file contents"), - path: z.string().optional().describe("The directory to search in. Defaults to the current working directory."), - include: z.string().optional().describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'), - }), + parameters: Parameters, execute: (params: { pattern: string; path?: string; include?: string }, ctx: Tool.Context) => Effect.gen(function* () { const empty = { diff --git a/packages/opencode/src/tool/invalid.ts b/packages/opencode/src/tool/invalid.ts index aca3618b6..b8d145d0b 100644 --- a/packages/opencode/src/tool/invalid.ts +++ b/packages/opencode/src/tool/invalid.ts @@ -1,15 +1,16 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" +export const Parameters = Schema.Struct({ + tool: Schema.String, + error: Schema.String, +}) + export const InvalidTool = Tool.define( "invalid", Effect.succeed({ description: "Do not use", - parameters: z.object({ - tool: z.string(), - error: z.string(), - }), + parameters: Parameters, execute: (params: { tool: string; error: string }) => Effect.succeed({ title: "Invalid Tool", diff --git a/packages/opencode/src/tool/lsp.ts b/packages/opencode/src/tool/lsp.ts index 0a0edc61e..29c6a8d84 100644 --- a/packages/opencode/src/tool/lsp.ts +++ b/packages/opencode/src/tool/lsp.ts @@ -1,5 +1,4 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import path from "path" import { LSP } from "../lsp" @@ -21,6 +20,17 @@ const operations = [ "outgoingCalls", ] as const +export const Parameters = Schema.Struct({ + operation: Schema.Literals(operations).annotate({ description: "The LSP operation to perform" }), + filePath: Schema.String.annotate({ description: "The absolute or relative path to the file" }), + line: Schema.Number.check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(1)) + .annotate({ description: "The line number (1-based, as shown in editors)" }), + character: Schema.Number.check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(1)) + .annotate({ description: "The character offset (1-based, as shown in editors)" }), +}) + export const LspTool = Tool.define( "lsp", Effect.gen(function* () { @@ -29,12 +39,7 @@ export const LspTool = Tool.define( return { description: DESCRIPTION, - parameters: z.object({ - operation: z.enum(operations).describe("The LSP operation to perform"), - filePath: z.string().describe("The absolute or relative path to the file"), - line: z.number().int().min(1).describe("The line number (1-based, as shown in editors)"), - character: z.number().int().min(1).describe("The character offset (1-based, as shown in editors)"), - }), + parameters: Parameters, execute: ( args: { operation: (typeof operations)[number]; filePath: string; line: number; character: number }, ctx: Tool.Context, diff --git a/packages/opencode/src/tool/plan.ts b/packages/opencode/src/tool/plan.ts index fd7276e09..8e2f11360 100644 --- a/packages/opencode/src/tool/plan.ts +++ b/packages/opencode/src/tool/plan.ts @@ -1,6 +1,5 @@ -import z from "zod" import path from "path" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import { Question } from "../question" import { Session } from "../session" @@ -17,6 +16,8 @@ function getLastModel(sessionID: SessionID) { return undefined } +export const Parameters = Schema.Struct({}) + export const PlanExitTool = Tool.define( "plan_exit", Effect.gen(function* () { @@ -26,7 +27,7 @@ export const PlanExitTool = Tool.define( return { description: EXIT_DESCRIPTION, - parameters: z.object({}), + parameters: Parameters, execute: (_params: {}, ctx: Tool.Context) => Effect.gen(function* () { const info = yield* session.get(ctx.sessionID) diff --git a/packages/opencode/src/tool/question.ts b/packages/opencode/src/tool/question.ts index e5bb33aa6..51f1e71e2 100644 --- a/packages/opencode/src/tool/question.ts +++ b/packages/opencode/src/tool/question.ts @@ -1,26 +1,25 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import { Question } from "../question" import DESCRIPTION from "./question.txt" -const parameters = z.object({ - questions: z.array(Question.Prompt.zod).describe("Questions to ask"), +export const Parameters = Schema.Struct({ + questions: Schema.mutable(Schema.Array(Question.Prompt)).annotate({ description: "Questions to ask" }), }) type Metadata = { answers: ReadonlyArray } -export const QuestionTool = Tool.define( +export const QuestionTool = Tool.define( "question", Effect.gen(function* () { const question = yield* Question.Service return { description: DESCRIPTION, - parameters, - execute: (params: z.infer, ctx: Tool.Context) => + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { const answers = yield* question.ask({ sessionID: ctx.sessionID, diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index a9b95346a..e7bfc6af3 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -1,5 +1,4 @@ -import z from "zod" -import { Effect, Option, Scope } from "effect" +import { Effect, Option, Schema, Scope } from "effect" import { createReadStream } from "fs" import * as path from "path" import { createInterface } from "readline" @@ -19,10 +18,19 @@ const MAX_BYTES = 50 * 1024 const MAX_BYTES_LABEL = `${MAX_BYTES / 1024} KB` const SAMPLE_BYTES = 4096 -const parameters = z.object({ - filePath: z.string().describe("The absolute path to the file or directory to read"), - offset: z.coerce.number().describe("The line number to start reading from (1-indexed)").optional(), - limit: z.coerce.number().describe("The maximum number of lines to read (defaults to 2000)").optional(), +// `offset` and `limit` were originally `z.coerce.number()` — the runtime +// coercion was useful when the tool was called from a shell but serves no +// purpose in the LLM tool-call path (the model emits typed JSON). The JSON +// Schema output is identical (`type: "number"`), so the LLM view is +// unchanged; purely CLI-facing uses must now send numbers rather than strings. +export const Parameters = Schema.Struct({ + filePath: Schema.String.annotate({ description: "The absolute path to the file or directory to read" }), + offset: Schema.optional(Schema.Number).annotate({ + description: "The line number to start reading from (1-indexed)", + }), + limit: Schema.optional(Schema.Number).annotate({ + description: "The maximum number of lines to read (defaults to 2000)", + }), }) export const ReadTool = Tool.define( @@ -140,7 +148,7 @@ export const ReadTool = Tool.define( return nonPrintableCount / bytes.length > 0.3 } - const run = Effect.fn("ReadTool.execute")(function* (params: z.infer, ctx: Tool.Context) { + const run = Effect.fn("ReadTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { if (params.offset !== undefined && params.offset < 1) { return yield* Effect.fail(new Error("offset must be greater than or equal to 1")) } @@ -275,8 +283,8 @@ export const ReadTool = Tool.define( return { description: DESCRIPTION, - parameters, - execute: (params: z.infer, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 0211e33bc..539ad6320 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -15,7 +15,9 @@ import { SkillTool } from "./skill" import * as Tool from "./tool" import { Config } from "../config" import { type ToolContext as PluginToolContext, type ToolDefinition } from "@opencode-ai/plugin" +import { Schema } from "effect" import z from "zod" +import { ZodOverride } from "@/util/effect-zod" import { Plugin } from "../plugin" import { Provider } from "../provider" import { ProviderID, type ModelID } from "../provider/schema" @@ -120,9 +122,17 @@ export const layer: Layer.Layer< const custom: Tool.Def[] = [] function fromPlugin(id: string, def: ToolDefinition): Tool.Def { + // Plugin tools define their args as a raw Zod shape. Wrap the + // derived Zod object in a `Schema.declare` so it slots into the + // Schema-typed framework, and annotate with `ZodOverride` so the + // walker emits the original Zod object for LLM JSON Schema. + const zodParams = z.object(def.args) + const parameters = Schema.declare((u): u is unknown => zodParams.safeParse(u).success).annotate({ + [ZodOverride]: zodParams, + }) return { id, - parameters: z.object(def.args), + parameters, description: def.description, execute: (args, toolCtx) => Effect.gen(function* () { diff --git a/packages/opencode/src/tool/skill.ts b/packages/opencode/src/tool/skill.ts index d86faec2b..8c41077be 100644 --- a/packages/opencode/src/tool/skill.ts +++ b/packages/opencode/src/tool/skill.ts @@ -1,15 +1,14 @@ import path from "path" import { pathToFileURL } from "url" -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Stream from "effect/Stream" import { Ripgrep } from "../file/ripgrep" import { Skill } from "../skill" import * as Tool from "./tool" import DESCRIPTION from "./skill.txt" -const Parameters = z.object({ - name: z.string().describe("The name of the skill from available_skills"), +export const Parameters = Schema.Struct({ + name: Schema.String.annotate({ description: "The name of the skill from available_skills" }), }) export const SkillTool = Tool.define( @@ -21,7 +20,7 @@ export const SkillTool = Tool.define( return { description: DESCRIPTION, parameters: Parameters, - execute: (params: z.infer, ctx: Tool.Context) => + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { const info = yield* skill.get(params.name) if (!info) { diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 3da0664f3..98f6bdd98 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -1,13 +1,12 @@ import * as Tool from "./tool" import DESCRIPTION from "./task.txt" -import z from "zod" import { Session } from "../session" import { SessionID, MessageID } from "../session/schema" import { MessageV2 } from "../session/message-v2" import { Agent } from "../agent/agent" import type { SessionPrompt } from "../session/prompt" import { Config } from "../config" -import { Effect } from "effect" +import { Effect, Schema } from "effect" export interface TaskPromptOps { cancel(sessionID: SessionID): void @@ -17,17 +16,15 @@ export interface TaskPromptOps { const id = "task" -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"), - task_id: z - .string() - .describe( +export const Parameters = Schema.Struct({ + description: Schema.String.annotate({ description: "A short (3-5 words) description of the task" }), + prompt: Schema.String.annotate({ description: "The task for the agent to perform" }), + subagent_type: Schema.String.annotate({ description: "The type of specialized agent to use for this task" }), + task_id: Schema.optional(Schema.String).annotate({ + description: "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(), + }), + command: Schema.optional(Schema.String).annotate({ description: "The command that triggered this task" }), }) export const TaskTool = Tool.define( @@ -37,7 +34,7 @@ export const TaskTool = Tool.define( const config = yield* Config.Service const sessions = yield* Session.Service - const run = Effect.fn("TaskTool.execute")(function* (params: z.infer, ctx: Tool.Context) { + const run = Effect.fn("TaskTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { const cfg = yield* config.get() if (!ctx.extra?.bypassAgentCheck) { @@ -168,8 +165,8 @@ export const TaskTool = Tool.define( return { description: DESCRIPTION, - parameters, - execute: (params: z.infer, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts index c08fb0411..c493d3a71 100644 --- a/packages/opencode/src/tool/todo.ts +++ b/packages/opencode/src/tool/todo.ts @@ -1,39 +1,34 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import * as Tool from "./tool" import DESCRIPTION_WRITE from "./todowrite.txt" import { Todo } from "../session/todo" -// Parameters are kept inline rather than derived from Todo.Info because -// Tool.define requires z.ZodObject-typed parameters for execute() inference, -// and zodObject(Todo.Info) returns ZodObject — reaching into .shape would -// erase field types. Tool schemas migrate to Effect Schema as a separate slice -// per specs/effect/schema.md. -const parameters = z.object({ - todos: z - .array( - z.object({ - content: z.string().describe("Brief description of the task"), - status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"), - priority: z.string().describe("Priority level of the task: high, medium, low"), - }), - ) - .describe("The updated todo list"), +// Todo.Info is still a zod schema (session/todo.ts). Inline the field shape +// here rather than referencing its `.shape` — the LLM-visible JSON Schema is +// identical, and it removes the last zod dependency from this tool. +const TodoItem = Schema.Struct({ + content: Schema.String.annotate({ description: "Brief description of the task" }), + status: Schema.String.annotate({ description: "Current status of the task: pending, in_progress, completed, cancelled" }), + priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }), +}) + +export const Parameters = Schema.Struct({ + todos: Schema.mutable(Schema.Array(TodoItem)).annotate({ description: "The updated todo list" }), }) type Metadata = { todos: Todo.Info[] } -export const TodoWriteTool = Tool.define( +export const TodoWriteTool = Tool.define( "todowrite", Effect.gen(function* () { const todo = yield* Todo.Service return { description: DESCRIPTION_WRITE, - parameters, - execute: (params: z.infer, ctx: Tool.Context) => + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { yield* ctx.ask({ permission: "todowrite", @@ -55,6 +50,6 @@ export const TodoWriteTool = Tool.define + } satisfies Tool.DefWithoutID }), ) diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 179149afd..c9115e9ff 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -1,5 +1,4 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import type { MessageV2 } from "../session/message-v2" import type { Permission } from "../permission" import type { SessionID, MessageID } from "../session/schema" @@ -32,29 +31,33 @@ export interface ExecuteResult { attachments?: Omit[] } -export interface Def { +export interface Def = Schema.Decoder, M extends Metadata = Metadata> { id: string description: string parameters: Parameters - execute(args: z.infer, ctx: Context): Effect.Effect> - formatValidationError?(error: z.ZodError): string + execute(args: Schema.Schema.Type, ctx: Context): Effect.Effect> + formatValidationError?(error: unknown): string } -export type DefWithoutID = Omit< +export type DefWithoutID = Schema.Decoder, M extends Metadata = Metadata> = Omit< Def, "id" > -export interface Info { +export interface Info = Schema.Decoder, M extends Metadata = Metadata> { id: string init: () => Effect.Effect> } -type Init = +type Init, M extends Metadata> = | DefWithoutID | (() => Effect.Effect>) export type InferParameters = - T extends Info ? z.infer

    : T extends Effect.Effect, any, any> ? z.infer

    : never + T extends Info + ? Schema.Schema.Type

    + : T extends Effect.Effect, any, any> + ? Schema.Schema.Type

    + : never export type InferMetadata = T extends Info ? M : T extends Effect.Effect, any, any> ? M : never @@ -65,7 +68,7 @@ export type InferDef = ? Def : never -function wrap( +function wrap, Result extends Metadata>( id: string, init: Init, truncate: Truncate.Interface, @@ -74,6 +77,10 @@ function wrap( return () => Effect.gen(function* () { const toolInfo = typeof init === "function" ? { ...(yield* init()) } : { ...init } + // Compile the parser closure once per tool init; `decodeUnknownEffect` + // allocates a new closure per call, so hoisting avoids re-closing it for + // every LLM tool invocation. + const decode = Schema.decodeUnknownEffect(toolInfo.parameters) const execute = toolInfo.execute toolInfo.execute = (args, ctx) => { const attrs = { @@ -83,19 +90,17 @@ function wrap( ...(ctx.callID ? { "tool.call_id": ctx.callID } : {}), } return Effect.gen(function* () { - yield* Effect.try({ - try: () => toolInfo.parameters.parse(args), - catch: (error) => { - if (error instanceof z.ZodError && toolInfo.formatValidationError) { - return new Error(toolInfo.formatValidationError(error), { cause: error }) - } - return new Error( - `The ${id} tool was called with invalid arguments: ${error}.\nPlease rewrite the input so it satisfies the expected schema.`, - { cause: error }, - ) - }, - }) - const result = yield* execute(args, ctx) + const decoded = yield* decode(args).pipe( + Effect.mapError((error) => + toolInfo.formatValidationError + ? new Error(toolInfo.formatValidationError(error), { cause: error }) + : new Error( + `The ${id} tool was called with invalid arguments: ${error}.\nPlease rewrite the input so it satisfies the expected schema.`, + { cause: error }, + ), + ), + ) + const result = yield* execute(decoded as Schema.Schema.Type, ctx) if (result.metadata.truncated !== undefined) { return result } @@ -116,7 +121,7 @@ function wrap( }) } -export function define( +export function define, Result extends Metadata, R, ID extends string = string>( id: ID, init: Effect.Effect, never, R>, ): Effect.Effect, never, R | Truncate.Service | Agent.Service> & { id: ID } { @@ -131,7 +136,7 @@ export function define(info: Info): Effect.Effect> { +export function init

    , M extends Metadata>(info: Info): Effect.Effect> { return Effect.gen(function* () { const init = yield* info.init() return { diff --git a/packages/opencode/src/tool/webfetch.ts b/packages/opencode/src/tool/webfetch.ts index 1d988b8d4..d2561a130 100644 --- a/packages/opencode/src/tool/webfetch.ts +++ b/packages/opencode/src/tool/webfetch.ts @@ -1,5 +1,4 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import { HttpClient, HttpClientRequest } from "effect/unstable/http" import * as Tool from "./tool" import TurndownService from "turndown" @@ -10,13 +9,14 @@ const MAX_RESPONSE_SIZE = 5 * 1024 * 1024 // 5MB const DEFAULT_TIMEOUT = 30 * 1000 // 30 seconds const MAX_TIMEOUT = 120 * 1000 // 2 minutes -const parameters = z.object({ - url: z.string().describe("The URL to fetch content from"), - format: z - .enum(["text", "markdown", "html"]) - .default("markdown") - .describe("The format to return the content in (text, markdown, or html). Defaults to markdown."), - timeout: z.number().describe("Optional timeout in seconds (max 120)").optional(), +export const Parameters = Schema.Struct({ + url: Schema.String.annotate({ description: "The URL to fetch content from" }), + format: Schema.Literals(["text", "markdown", "html"]) + .pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed("markdown" as const))) + .annotate({ + description: "The format to return the content in (text, markdown, or html). Defaults to markdown.", + }), + timeout: Schema.optional(Schema.Number).annotate({ description: "Optional timeout in seconds (max 120)" }), }) export const WebFetchTool = Tool.define( @@ -27,8 +27,8 @@ export const WebFetchTool = Tool.define( return { description: DESCRIPTION, - parameters, - execute: (params: z.infer, ctx: Tool.Context) => + parameters: Parameters, + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { if (!params.url.startsWith("http://") && !params.url.startsWith("https://")) { throw new Error("URL must start with http:// or https://") diff --git a/packages/opencode/src/tool/websearch.ts b/packages/opencode/src/tool/websearch.ts index 34cefd031..ff4c696a2 100644 --- a/packages/opencode/src/tool/websearch.ts +++ b/packages/opencode/src/tool/websearch.ts @@ -1,27 +1,24 @@ -import z from "zod" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import { HttpClient } from "effect/unstable/http" import * as Tool from "./tool" import * as McpExa from "./mcp-exa" import DESCRIPTION from "./websearch.txt" -const Parameters = z.object({ - query: z.string().describe("Websearch query"), - numResults: z.number().optional().describe("Number of search results to return (default: 8)"), - livecrawl: z - .enum(["fallback", "preferred"]) - .optional() - .describe( +export const Parameters = Schema.Struct({ + query: Schema.String.annotate({ description: "Websearch query" }), + numResults: Schema.optional(Schema.Number).annotate({ + description: "Number of search results to return (default: 8)", + }), + livecrawl: Schema.optional(Schema.Literals(["fallback", "preferred"])).annotate({ + description: "Live crawl mode - 'fallback': use live crawling as backup if cached content unavailable, 'preferred': prioritize live crawling (default: 'fallback')", - ), - type: z - .enum(["auto", "fast", "deep"]) - .optional() - .describe("Search type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search"), - contextMaxCharacters: z - .number() - .optional() - .describe("Maximum characters for context string optimized for LLMs (default: 10000)"), + }), + type: Schema.optional(Schema.Literals(["auto", "fast", "deep"])).annotate({ + description: "Search type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search", + }), + contextMaxCharacters: Schema.optional(Schema.Number).annotate({ + description: "Maximum characters for context string optimized for LLMs (default: 10000)", + }), }) export const WebSearchTool = Tool.define( @@ -34,7 +31,7 @@ export const WebSearchTool = Tool.define( return DESCRIPTION.replace("{{year}}", new Date().getFullYear().toString()) }, parameters: Parameters, - execute: (params: z.infer, ctx: Tool.Context) => + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { yield* ctx.ask({ permission: "websearch", diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 80198f455..b52f4a164 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -1,4 +1,4 @@ -import z from "zod" +import { Schema } from "effect" import * as path from "path" import { Effect } from "effect" import * as Tool from "./tool" @@ -17,6 +17,13 @@ import * as Bom from "@/util/bom" const MAX_PROJECT_DIAGNOSTICS_FILES = 5 +export const Parameters = Schema.Struct({ + content: Schema.String.annotate({ description: "The content to write to the file" }), + filePath: Schema.String.annotate({ + description: "The absolute path to the file to write (must be absolute, not relative)", + }), +}) + export const WriteTool = Tool.define( "write", Effect.gen(function* () { @@ -27,10 +34,7 @@ export const WriteTool = Tool.define( return { description: DESCRIPTION, - parameters: z.object({ - content: z.string().describe("The content to write to the file"), - filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"), - }), + parameters: Parameters, execute: (params: { content: string; filePath: string }, ctx: Tool.Context) => Effect.gen(function* () { const filepath = path.isAbsolute(params.filePath) diff --git a/packages/opencode/src/util/effect-zod.ts b/packages/opencode/src/util/effect-zod.ts index 24ff9a10e..2bbad2dd7 100644 --- a/packages/opencode/src/util/effect-zod.ts +++ b/packages/opencode/src/util/effect-zod.ts @@ -49,6 +49,17 @@ function isZodType(value: unknown): value is z.ZodTypeAny { return typeof value === "object" && value !== null && "_zod" in value } +/** + * Emit a JSON Schema for a tool/route parameter schema — derives the zod form + * via the walker so Effect Schema inputs flow through the same zod-openapi + * pipeline the LLM/SDK layer already depends on. `io: "input"` mirrors what + * `session/prompt.ts` has always passed to `ai`'s `jsonSchema()` helper. + */ +export function toJsonSchema(schema: S) { + return z.toJSONSchema(zod(schema), { io: "input" }) +} + + function walk(ast: SchemaAST.AST): z.ZodTypeAny { const cached = walkCache.get(ast) if (cached) return cached diff --git a/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap b/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap new file mode 100644 index 000000000..eb3fe6cce --- /dev/null +++ b/packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap @@ -0,0 +1,495 @@ +// Bun Snapshot v1, https://bun.sh/docs/test/snapshots + +exports[`tool parameters JSON Schema (wire shape) apply_patch 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "patchText": { + "description": "The full patch text that describes all changes to be made", + "type": "string", + }, + }, + "required": [ + "patchText", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) bash 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "command": { + "description": "The command to execute", + "type": "string", + }, + "description": { + "description": +"Clear, concise description of what this command does in 5-10 words. Examples: +Input: ls +Output: Lists files in current directory + +Input: git status +Output: Shows working tree status + +Input: npm install +Output: Installs package dependencies + +Input: mkdir foo +Output: Creates directory 'foo'" +, + "type": "string", + }, + "timeout": { + "description": "Optional timeout in milliseconds", + "type": "number", + }, + "workdir": { + "description": "The working directory to run the command in. Defaults to the current directory. Use this instead of 'cd' commands.", + "type": "string", + }, + }, + "required": [ + "command", + "description", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) codesearch 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "query": { + "description": "Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'", + "type": "string", + }, + "tokensNum": { + "default": 5000, + "description": "Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.", + "maximum": 50000, + "minimum": 1000, + "type": "number", + }, + }, + "required": [ + "query", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) edit 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "filePath": { + "description": "The absolute path to the file to modify", + "type": "string", + }, + "newString": { + "description": "The text to replace it with (must be different from oldString)", + "type": "string", + }, + "oldString": { + "description": "The text to replace", + "type": "string", + }, + "replaceAll": { + "description": "Replace all occurrences of oldString (default false)", + "type": "boolean", + }, + }, + "required": [ + "filePath", + "oldString", + "newString", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) glob 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "path": { + "description": "The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.", + "type": "string", + }, + "pattern": { + "description": "The glob pattern to match files against", + "type": "string", + }, + }, + "required": [ + "pattern", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) grep 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "include": { + "description": "File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")", + "type": "string", + }, + "path": { + "description": "The directory to search in. Defaults to the current working directory.", + "type": "string", + }, + "pattern": { + "description": "The regex pattern to search for in file contents", + "type": "string", + }, + }, + "required": [ + "pattern", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) invalid 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "error": { + "type": "string", + }, + "tool": { + "type": "string", + }, + }, + "required": [ + "tool", + "error", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) lsp 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "character": { + "description": "The character offset (1-based, as shown in editors)", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer", + }, + "filePath": { + "description": "The absolute or relative path to the file", + "type": "string", + }, + "line": { + "description": "The line number (1-based, as shown in editors)", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer", + }, + "operation": { + "description": "The LSP operation to perform", + "enum": [ + "goToDefinition", + "findReferences", + "hover", + "documentSymbol", + "workspaceSymbol", + "goToImplementation", + "prepareCallHierarchy", + "incomingCalls", + "outgoingCalls", + ], + "type": "string", + }, + }, + "required": [ + "operation", + "filePath", + "line", + "character", + ], + "type": "object", +} +`; + + +exports[`tool parameters JSON Schema (wire shape) plan 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {}, + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) question 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "questions": { + "description": "Questions to ask", + "items": { + "properties": { + "header": { + "description": "Very short label (max 30 chars)", + "type": "string", + }, + "multiple": { + "description": "Allow selecting multiple choices", + "type": "boolean", + }, + "options": { + "description": "Available choices", + "items": { + "properties": { + "description": { + "description": "Explanation of choice", + "type": "string", + }, + "label": { + "description": "Display text (1-5 words, concise)", + "type": "string", + }, + }, + "ref": "QuestionOption", + "required": [ + "label", + "description", + ], + "type": "object", + }, + "type": "array", + }, + "question": { + "description": "Complete question", + "type": "string", + }, + }, + "ref": "QuestionPrompt", + "required": [ + "question", + "header", + "options", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": [ + "questions", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) read 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "filePath": { + "description": "The absolute path to the file or directory to read", + "type": "string", + }, + "limit": { + "description": "The maximum number of lines to read (defaults to 2000)", + "type": "number", + }, + "offset": { + "description": "The line number to start reading from (1-indexed)", + "type": "number", + }, + }, + "required": [ + "filePath", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) skill 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "name": { + "description": "The name of the skill from available_skills", + "type": "string", + }, + }, + "required": [ + "name", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) task 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "command": { + "description": "The command that triggered this task", + "type": "string", + }, + "description": { + "description": "A short (3-5 words) description of the task", + "type": "string", + }, + "prompt": { + "description": "The task for the agent to perform", + "type": "string", + }, + "subagent_type": { + "description": "The type of specialized agent to use for this task", + "type": "string", + }, + "task_id": { + "description": "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)", + "type": "string", + }, + }, + "required": [ + "description", + "prompt", + "subagent_type", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) todo 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "todos": { + "description": "The updated todo list", + "items": { + "properties": { + "content": { + "description": "Brief description of the task", + "type": "string", + }, + "priority": { + "description": "Priority level of the task: high, medium, low", + "type": "string", + }, + "status": { + "description": "Current status of the task: pending, in_progress, completed, cancelled", + "type": "string", + }, + }, + "required": [ + "content", + "status", + "priority", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": [ + "todos", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) webfetch 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "format": { + "default": "markdown", + "description": "The format to return the content in (text, markdown, or html). Defaults to markdown.", + "enum": [ + "text", + "markdown", + "html", + ], + "type": "string", + }, + "timeout": { + "description": "Optional timeout in seconds (max 120)", + "type": "number", + }, + "url": { + "description": "The URL to fetch content from", + "type": "string", + }, + }, + "required": [ + "url", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) websearch 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "contextMaxCharacters": { + "description": "Maximum characters for context string optimized for LLMs (default: 10000)", + "type": "number", + }, + "livecrawl": { + "description": "Live crawl mode - 'fallback': use live crawling as backup if cached content unavailable, 'preferred': prioritize live crawling (default: 'fallback')", + "enum": [ + "fallback", + "preferred", + ], + "type": "string", + }, + "numResults": { + "description": "Number of search results to return (default: 8)", + "type": "number", + }, + "query": { + "description": "Websearch query", + "type": "string", + }, + "type": { + "description": "Search type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search", + "enum": [ + "auto", + "fast", + "deep", + ], + "type": "string", + }, + }, + "required": [ + "query", + ], + "type": "object", +} +`; + +exports[`tool parameters JSON Schema (wire shape) write 1`] = ` +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "content": { + "description": "The content to write to the file", + "type": "string", + }, + "filePath": { + "description": "The absolute path to the file to write (must be absolute, not relative)", + "type": "string", + }, + }, + "required": [ + "content", + "filePath", + ], + "type": "object", +} +`; diff --git a/packages/opencode/test/tool/parameters.test.ts b/packages/opencode/test/tool/parameters.test.ts new file mode 100644 index 000000000..8ea008a45 --- /dev/null +++ b/packages/opencode/test/tool/parameters.test.ts @@ -0,0 +1,260 @@ +import { describe, expect, test } from "bun:test" +import { Result, Schema } from "effect" +import { toJsonSchema } from "../../src/util/effect-zod" + +// Each tool exports its parameters schema at module scope so this test can +// import them without running the tool's Effect-based init. The JSON Schema +// snapshot captures what the LLM sees; the parse assertions pin down the +// accepts/rejects contract. `toJsonSchema` is the same helper `session/ +// prompt.ts` uses to emit tool schemas to the LLM, so the snapshots stay +// byte-identical regardless of whether a tool has migrated from zod to Schema. + +import { Parameters as ApplyPatch } from "../../src/tool/apply_patch" +import { Parameters as Bash } from "../../src/tool/bash" +import { Parameters as CodeSearch } from "../../src/tool/codesearch" +import { Parameters as Edit } from "../../src/tool/edit" +import { Parameters as Glob } from "../../src/tool/glob" +import { Parameters as Grep } from "../../src/tool/grep" +import { Parameters as Invalid } from "../../src/tool/invalid" +import { Parameters as Lsp } from "../../src/tool/lsp" +import { Parameters as Plan } from "../../src/tool/plan" +import { Parameters as Question } from "../../src/tool/question" +import { Parameters as Read } from "../../src/tool/read" +import { Parameters as Skill } from "../../src/tool/skill" +import { Parameters as Task } from "../../src/tool/task" +import { Parameters as Todo } from "../../src/tool/todo" +import { Parameters as WebFetch } from "../../src/tool/webfetch" +import { Parameters as WebSearch } from "../../src/tool/websearch" +import { Parameters as Write } from "../../src/tool/write" + +const parse = >(schema: S, input: unknown): S["Type"] => + Schema.decodeUnknownSync(schema)(input) + +const accepts = (schema: Schema.Decoder, input: unknown): boolean => + Result.isSuccess(Schema.decodeUnknownResult(schema)(input)) + +describe("tool parameters", () => { + describe("JSON Schema (wire shape)", () => { + test("apply_patch", () => expect(toJsonSchema(ApplyPatch)).toMatchSnapshot()) + test("bash", () => expect(toJsonSchema(Bash)).toMatchSnapshot()) + test("codesearch", () => expect(toJsonSchema(CodeSearch)).toMatchSnapshot()) + test("edit", () => expect(toJsonSchema(Edit)).toMatchSnapshot()) + test("glob", () => expect(toJsonSchema(Glob)).toMatchSnapshot()) + test("grep", () => expect(toJsonSchema(Grep)).toMatchSnapshot()) + test("invalid", () => expect(toJsonSchema(Invalid)).toMatchSnapshot()) + test("lsp", () => expect(toJsonSchema(Lsp)).toMatchSnapshot()) + test("plan", () => expect(toJsonSchema(Plan)).toMatchSnapshot()) + test("question", () => expect(toJsonSchema(Question)).toMatchSnapshot()) + test("read", () => expect(toJsonSchema(Read)).toMatchSnapshot()) + test("skill", () => expect(toJsonSchema(Skill)).toMatchSnapshot()) + test("task", () => expect(toJsonSchema(Task)).toMatchSnapshot()) + test("todo", () => expect(toJsonSchema(Todo)).toMatchSnapshot()) + test("webfetch", () => expect(toJsonSchema(WebFetch)).toMatchSnapshot()) + test("websearch", () => expect(toJsonSchema(WebSearch)).toMatchSnapshot()) + test("write", () => expect(toJsonSchema(Write)).toMatchSnapshot()) + }) + + describe("apply_patch", () => { + test("accepts patchText", () => { + expect(parse(ApplyPatch, { patchText: "*** Begin Patch\n*** End Patch" })).toEqual({ + patchText: "*** Begin Patch\n*** End Patch", + }) + }) + test("rejects missing patchText", () => { + expect(accepts(ApplyPatch, {})).toBe(false) + }) + test("rejects non-string patchText", () => { + expect(accepts(ApplyPatch, { patchText: 123 })).toBe(false) + }) + }) + + describe("bash", () => { + test("accepts minimum: command + description", () => { + expect(parse(Bash, { command: "ls", description: "list" })).toEqual({ command: "ls", description: "list" }) + }) + test("accepts optional timeout + workdir", () => { + const parsed = parse(Bash, { command: "ls", description: "list", timeout: 5000, workdir: "/tmp" }) + expect(parsed.timeout).toBe(5000) + expect(parsed.workdir).toBe("/tmp") + }) + test("rejects missing description (required by zod)", () => { + expect(accepts(Bash, { command: "ls" })).toBe(false) + }) + test("rejects missing command", () => { + expect(accepts(Bash, { description: "list" })).toBe(false) + }) + }) + + describe("codesearch", () => { + test("accepts query; tokensNum defaults to 5000", () => { + expect(parse(CodeSearch, { query: "hooks" })).toEqual({ query: "hooks", tokensNum: 5000 }) + }) + test("accepts override tokensNum", () => { + expect(parse(CodeSearch, { query: "hooks", tokensNum: 10000 }).tokensNum).toBe(10000) + }) + test("rejects tokensNum under 1000", () => { + expect(accepts(CodeSearch, { query: "x", tokensNum: 500 })).toBe(false) + }) + test("rejects tokensNum over 50000", () => { + expect(accepts(CodeSearch, { query: "x", tokensNum: 60000 })).toBe(false) + }) + }) + + describe("edit", () => { + test("accepts all four fields", () => { + expect(parse(Edit, { filePath: "/a", oldString: "x", newString: "y", replaceAll: true })).toEqual({ + filePath: "/a", + oldString: "x", + newString: "y", + replaceAll: true, + }) + }) + test("replaceAll is optional", () => { + const parsed = parse(Edit, { filePath: "/a", oldString: "x", newString: "y" }) + expect(parsed.replaceAll).toBeUndefined() + }) + test("rejects missing filePath", () => { + expect(accepts(Edit, { oldString: "x", newString: "y" })).toBe(false) + }) + }) + + describe("glob", () => { + test("accepts pattern-only", () => { + expect(parse(Glob, { pattern: "**/*.ts" })).toEqual({ pattern: "**/*.ts" }) + }) + test("accepts optional path", () => { + expect(parse(Glob, { pattern: "**/*.ts", path: "/tmp" }).path).toBe("/tmp") + }) + test("rejects missing pattern", () => { + expect(accepts(Glob, {})).toBe(false) + }) + }) + + describe("grep", () => { + test("accepts pattern-only", () => { + expect(parse(Grep, { pattern: "TODO" })).toEqual({ pattern: "TODO" }) + }) + test("accepts optional path + include", () => { + const parsed = parse(Grep, { pattern: "TODO", path: "/tmp", include: "*.ts" }) + expect(parsed.path).toBe("/tmp") + expect(parsed.include).toBe("*.ts") + }) + test("rejects missing pattern", () => { + expect(accepts(Grep, {})).toBe(false) + }) + }) + + describe("invalid", () => { + test("accepts tool + error", () => { + expect(parse(Invalid, { tool: "foo", error: "bar" })).toEqual({ tool: "foo", error: "bar" }) + }) + test("rejects missing fields", () => { + expect(accepts(Invalid, { tool: "foo" })).toBe(false) + expect(accepts(Invalid, { error: "bar" })).toBe(false) + }) + }) + + describe("lsp", () => { + test("accepts all fields", () => { + const parsed = parse(Lsp, { operation: "hover", filePath: "/a.ts", line: 1, character: 1 }) + expect(parsed.operation).toBe("hover") + }) + test("rejects line < 1", () => { + expect(accepts(Lsp, { operation: "hover", filePath: "/a.ts", line: 0, character: 1 })).toBe(false) + }) + test("rejects character < 1", () => { + expect(accepts(Lsp, { operation: "hover", filePath: "/a.ts", line: 1, character: 0 })).toBe(false) + }) + test("rejects unknown operation", () => { + expect(accepts(Lsp, { operation: "bogus", filePath: "/a.ts", line: 1, character: 1 })).toBe(false) + }) + }) + + describe("plan", () => { + test("accepts empty object", () => { + expect(parse(Plan, {})).toEqual({}) + }) + }) + + describe("question", () => { + test("accepts questions array", () => { + const parsed = parse(Question, { + questions: [ + { + question: "pick one", + header: "Header", + custom: false, + options: [{ label: "a", description: "desc" }], + }, + ], + }) + expect(parsed.questions.length).toBe(1) + }) + test("rejects missing questions", () => { + expect(accepts(Question, {})).toBe(false) + }) + }) + + describe("read", () => { + test("accepts filePath-only", () => { + expect(parse(Read, { filePath: "/a" }).filePath).toBe("/a") + }) + test("accepts optional offset + limit", () => { + const parsed = parse(Read, { filePath: "/a", offset: 10, limit: 100 }) + expect(parsed.offset).toBe(10) + expect(parsed.limit).toBe(100) + }) + }) + + describe("skill", () => { + test("accepts name", () => { + expect(parse(Skill, { name: "foo" }).name).toBe("foo") + }) + test("rejects missing name", () => { + expect(accepts(Skill, {})).toBe(false) + }) + }) + + describe("task", () => { + test("accepts description + prompt + subagent_type", () => { + const parsed = parse(Task, { description: "d", prompt: "p", subagent_type: "general" }) + expect(parsed.subagent_type).toBe("general") + }) + test("rejects missing prompt", () => { + expect(accepts(Task, { description: "d", subagent_type: "general" })).toBe(false) + }) + }) + + describe("todo", () => { + test("accepts todos array", () => { + const parsed = parse(Todo, { + todos: [{ id: "t1", content: "do x", status: "pending", priority: "medium" }], + }) + expect(parsed.todos.length).toBe(1) + }) + test("rejects missing todos", () => { + expect(accepts(Todo, {})).toBe(false) + }) + }) + + describe("webfetch", () => { + test("accepts url-only", () => { + expect(parse(WebFetch, { url: "https://example.com" }).url).toBe("https://example.com") + }) + }) + + describe("websearch", () => { + test("accepts query", () => { + expect(parse(WebSearch, { query: "opencode" }).query).toBe("opencode") + }) + }) + + describe("write", () => { + test("accepts content + filePath", () => { + expect(parse(Write, { content: "hi", filePath: "/a" })).toEqual({ content: "hi", filePath: "/a" }) + }) + test("rejects missing filePath", () => { + expect(accepts(Write, { content: "hi" })).toBe(false) + }) + }) +}) diff --git a/packages/opencode/test/tool/tool-define.test.ts b/packages/opencode/test/tool/tool-define.test.ts index 00d1e039a..283708767 100644 --- a/packages/opencode/test/tool/tool-define.test.ts +++ b/packages/opencode/test/tool/tool-define.test.ts @@ -1,13 +1,13 @@ import { describe, test, expect } from "bun:test" -import { Effect, Layer, ManagedRuntime } from "effect" -import z from "zod" +import { Effect, Layer, ManagedRuntime, Schema } from "effect" import { Agent } from "../../src/agent/agent" +import { MessageID, SessionID } from "../../src/session/schema" import { Tool } from "../../src/tool" import { Truncate } from "../../src/tool" const runtime = ManagedRuntime.make(Layer.mergeAll(Truncate.defaultLayer, Agent.defaultLayer)) -const params = z.object({ input: z.string() }) +const params = Schema.Struct({ input: Schema.String }) function makeTool(id: string, executeFn?: () => void) { return { @@ -56,4 +56,44 @@ describe("Tool.define", () => { expect(first).not.toBe(second) }) + + test("execute receives decoded parameters", async () => { + const parameters = Schema.Struct({ + count: Schema.NumberFromString.pipe(Schema.optional, Schema.withDecodingDefaultType(Effect.succeed(5))), + }) + const calls: Array> = [] + const info = await runtime.runPromise( + Tool.define( + "test-decoded", + Effect.succeed({ + description: "test tool", + parameters, + execute(args: Schema.Schema.Type) { + calls.push(args) + return Effect.succeed({ title: "test", output: "ok", metadata: { truncated: false } }) + }, + }), + ), + ) + const ctx: Tool.Context = { + sessionID: SessionID.descending(), + messageID: MessageID.ascending(), + agent: "build", + abort: new AbortController().signal, + messages: [], + metadata() { + return Effect.void + }, + ask() { + return Effect.void + }, + } + const tool = await Effect.runPromise(info.init()) + const execute = tool.execute as unknown as (args: unknown, ctx: Tool.Context) => ReturnType + + await Effect.runPromise(execute({}, ctx)) + await Effect.runPromise(execute({ count: "7" }, ctx)) + + expect(calls).toEqual([{ count: 5 }, { count: 7 }]) + }) }) From 3f8c659056c3ddf3e9074efb8a1ea57018c356ab Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 20:10:56 +0000 Subject: [PATCH 093/426] chore: generate --- packages/opencode/src/tool/apply_patch.ts | 8 +++++-- packages/opencode/src/tool/read.ts | 8 +++++-- packages/opencode/src/tool/task.ts | 8 +++++-- packages/opencode/src/tool/todo.ts | 4 +++- packages/opencode/src/tool/tool.ts | 29 ++++++++++++++++------- packages/opencode/src/util/effect-zod.ts | 1 - 6 files changed, 42 insertions(+), 16 deletions(-) diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts index effc428c7..72f24a3f6 100644 --- a/packages/opencode/src/tool/apply_patch.ts +++ b/packages/opencode/src/tool/apply_patch.ts @@ -27,7 +27,10 @@ export const ApplyPatchTool = Tool.define( const format = yield* Format.Service const bus = yield* Bus.Service - const run = Effect.fn("ApplyPatchTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { + const run = Effect.fn("ApplyPatchTool.execute")(function* ( + params: Schema.Schema.Type, + ctx: Tool.Context, + ) { if (!params.patchText) { return yield* Effect.fail(new Error("patchText is required")) } @@ -297,7 +300,8 @@ export const ApplyPatchTool = Tool.define( return { description: DESCRIPTION, parameters: Parameters, - execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => + run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index e7bfc6af3..d0995626c 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -148,7 +148,10 @@ export const ReadTool = Tool.define( return nonPrintableCount / bytes.length > 0.3 } - const run = Effect.fn("ReadTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { + const run = Effect.fn("ReadTool.execute")(function* ( + params: Schema.Schema.Type, + ctx: Tool.Context, + ) { if (params.offset !== undefined && params.offset < 1) { return yield* Effect.fail(new Error("offset must be greater than or equal to 1")) } @@ -284,7 +287,8 @@ export const ReadTool = Tool.define( return { description: DESCRIPTION, parameters: Parameters, - execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => + run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 98f6bdd98..5cb0dc6a8 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -34,7 +34,10 @@ export const TaskTool = Tool.define( const config = yield* Config.Service const sessions = yield* Session.Service - const run = Effect.fn("TaskTool.execute")(function* (params: Schema.Schema.Type, ctx: Tool.Context) { + const run = Effect.fn("TaskTool.execute")(function* ( + params: Schema.Schema.Type, + ctx: Tool.Context, + ) { const cfg = yield* config.get() if (!ctx.extra?.bypassAgentCheck) { @@ -166,7 +169,8 @@ export const TaskTool = Tool.define( return { description: DESCRIPTION, parameters: Parameters, - execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => + run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts index c493d3a71..18d21cf61 100644 --- a/packages/opencode/src/tool/todo.ts +++ b/packages/opencode/src/tool/todo.ts @@ -8,7 +8,9 @@ import { Todo } from "../session/todo" // identical, and it removes the last zod dependency from this tool. const TodoItem = Schema.Struct({ content: Schema.String.annotate({ description: "Brief description of the task" }), - status: Schema.String.annotate({ description: "Current status of the task: pending, in_progress, completed, cancelled" }), + status: Schema.String.annotate({ + description: "Current status of the task: pending, in_progress, completed, cancelled", + }), priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }), }) diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index c9115e9ff..7e753cb9b 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -31,19 +31,25 @@ export interface ExecuteResult { attachments?: Omit[] } -export interface Def = Schema.Decoder, M extends Metadata = Metadata> { +export interface Def< + Parameters extends Schema.Decoder = Schema.Decoder, + M extends Metadata = Metadata, +> { id: string description: string parameters: Parameters execute(args: Schema.Schema.Type, ctx: Context): Effect.Effect> formatValidationError?(error: unknown): string } -export type DefWithoutID = Schema.Decoder, M extends Metadata = Metadata> = Omit< - Def, - "id" -> +export type DefWithoutID< + Parameters extends Schema.Decoder = Schema.Decoder, + M extends Metadata = Metadata, +> = Omit, "id"> -export interface Info = Schema.Decoder, M extends Metadata = Metadata> { +export interface Info< + Parameters extends Schema.Decoder = Schema.Decoder, + M extends Metadata = Metadata, +> { id: string init: () => Effect.Effect> } @@ -121,7 +127,12 @@ function wrap, Result extends Metadat }) } -export function define, Result extends Metadata, R, ID extends string = string>( +export function define< + Parameters extends Schema.Decoder, + Result extends Metadata, + R, + ID extends string = string, +>( id: ID, init: Effect.Effect, never, R>, ): Effect.Effect, never, R | Truncate.Service | Agent.Service> & { id: ID } { @@ -136,7 +147,9 @@ export function define, Result extend ) } -export function init

    , M extends Metadata>(info: Info): Effect.Effect> { +export function init

    , M extends Metadata>( + info: Info, +): Effect.Effect> { return Effect.gen(function* () { const init = yield* info.init() return { diff --git a/packages/opencode/src/util/effect-zod.ts b/packages/opencode/src/util/effect-zod.ts index 2bbad2dd7..332a5c76e 100644 --- a/packages/opencode/src/util/effect-zod.ts +++ b/packages/opencode/src/util/effect-zod.ts @@ -59,7 +59,6 @@ export function toJsonSchema(schema: S) { return z.toJSONSchema(zod(schema), { io: "input" }) } - function walk(ast: SchemaAST.AST): z.ZodTypeAny { const cached = walkCache.get(ast) if (cached) return cached From 98ea5b6e7e907b6d4eb52b294b9a7770bbd0f18e Mon Sep 17 00:00:00 2001 From: James Long Date: Thu, 23 Apr 2026 16:19:19 -0400 Subject: [PATCH 094/426] feat(tui): support builtin protocol for handling context from editors (#24034) --- packages/opencode/src/cli/cmd/tui/app.tsx | 5 +- .../cmd/tui/component/prompt/autocomplete.tsx | 104 ++++-- .../cli/cmd/tui/component/prompt/index.tsx | 50 ++- .../src/cli/cmd/tui/context/editor.ts | 318 ++++++++++++++++++ 4 files changed, 448 insertions(+), 29 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/context/editor.ts diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 2b31d078c..eb5cb44e8 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -24,6 +24,7 @@ import { DialogProvider as DialogProviderList } from "@tui/component/dialog-prov import { ErrorComponent } from "@tui/component/error-component" import { PluginRouteMissing } from "@tui/component/plugin-route-missing" import { ProjectProvider } from "@tui/context/project" +import { EditorContextProvider } from "@tui/context/editor" import { useEvent } from "@tui/context/event" import { SDKProvider, useSDK } from "@tui/context/sdk" import { StartupLoading } from "@tui/component/startup-loading" @@ -177,7 +178,9 @@ export function tui(input: { - + + + diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 305d07622..4a5e1a4ca 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -1,9 +1,11 @@ import type { BoxRenderable, TextareaRenderable, KeyEvent, ScrollBoxRenderable } from "@opentui/core" import { pathToFileURL } from "bun" import fuzzysort from "fuzzysort" +import path from "path" import { firstBy } from "remeda" import { createMemo, createResource, createEffect, onMount, onCleanup, Index, Show, createSignal } from "solid-js" import { createStore } from "solid-js/store" +import { useEditorContext } from "@tui/context/editor" import { useSDK } from "@tui/context/sdk" import { useSync } from "@tui/context/sync" import { getScrollAcceleration } from "../../util/scroll" @@ -77,6 +79,7 @@ export function Autocomplete(props: { agentStyleId: number promptPartTypeId: () => number }) { + const editor = useEditorContext() const sdk = useSDK() const sync = useSync() const command = useCommandDialog() @@ -221,6 +224,70 @@ export function Autocomplete(props: { } } + function createFilePart(item: string, lineRange?: { startLine: number; endLine?: number }) { + const baseDir = (sync.path.directory || process.cwd()).replace(/\/+$/, "") + const fullPath = path.isAbsolute(item) ? item : path.join(baseDir, item) + const urlObj = pathToFileURL(fullPath) + const filename = + lineRange && !item.endsWith("/") + ? `${item}#${lineRange.startLine}${lineRange.endLine ? `-${lineRange.endLine}` : ""}` + : item + + if (lineRange && !item.endsWith("/")) { + urlObj.searchParams.set("start", String(lineRange.startLine)) + if (lineRange.endLine !== undefined) { + urlObj.searchParams.set("end", String(lineRange.endLine)) + } + } + + return { + filename, + url: urlObj.href, + part: { + type: "file" as const, + mime: "text/plain", + filename, + url: urlObj.href, + source: { + type: "file" as const, + text: { + start: 0, + end: 0, + value: "", + }, + path: item, + }, + }, + } + } + + function normalizeMentionPath(filePath: string) { + const baseDir = sync.path.directory || process.cwd() + const absolute = path.resolve(filePath) + const relative = path.relative(baseDir, absolute) + + if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) { + return relative.split(path.sep).join("/") + } + + return absolute.split(path.sep).join("/") + } + + function insertFileMention(input: { filePath: string; lineStart: number; lineEnd: number }) { + const item = normalizeMentionPath(input.filePath) + const lineRange = { + startLine: input.lineStart, + endLine: input.lineEnd > input.lineStart ? input.lineEnd : undefined, + } + const { filename, part } = createFilePart(item, lineRange) + const index = store.visible === "@" ? store.index : props.input().cursorOffset + + command.keybinds(true) + setStore("visible", false) + setStore("index", index) + insertPart(filename, part) + } + const [files] = createResource( () => search(), async (query) => { @@ -250,18 +317,7 @@ export function Autocomplete(props: { const width = props.anchor().width - 4 options.push( ...sortedFiles.map((item): AutocompleteOption => { - const baseDir = (sync.path.directory || process.cwd()).replace(/\/+$/, "") - const fullPath = `${baseDir}/${item}` - const urlObj = pathToFileURL(fullPath) - let filename = item - if (lineRange && !item.endsWith("/")) { - filename = `${item}#${lineRange.startLine}${lineRange.endLine ? `-${lineRange.endLine}` : ""}` - urlObj.searchParams.set("start", String(lineRange.startLine)) - if (lineRange.endLine !== undefined) { - urlObj.searchParams.set("end", String(lineRange.endLine)) - } - } - const url = urlObj.href + const { filename, url, part } = createFilePart(item, lineRange) const isDir = item.endsWith("/") return { @@ -270,21 +326,7 @@ export function Autocomplete(props: { isDirectory: isDir, path: item, onSelect: () => { - insertPart(filename, { - type: "file", - mime: "text/plain", - filename, - url, - source: { - type: "file", - text: { - start: 0, - end: 0, - value: "", - }, - path: item, - }, - }) + insertPart(filename, part) }, } }), @@ -501,6 +543,14 @@ export function Autocomplete(props: { } onMount(() => { + const unsubscribeMention = editor.onMention((mention) => { + insertFileMention(mention) + }) + + onCleanup(() => { + unsubscribeMention() + }) + props.ref({ get visible() { return store.visible diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 2e08e66a4..5288a819b 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -12,6 +12,7 @@ import { useRoute } from "@tui/context/route" import { useProject } from "@tui/context/project" import { useSync } from "@tui/context/sync" import { useEvent } from "@tui/context/event" +import { useEditorContext } from "@tui/context/editor" import { MessageID, PartID } from "@/session/schema" import { createStore, produce, unwrap } from "solid-js/store" import { useKeybind } from "@tui/context/keybind" @@ -21,7 +22,7 @@ import { usePromptStash } from "./stash" import { DialogStash } from "../dialog-stash" import { type AutocompleteRef, Autocomplete } from "./autocomplete" import { useCommandDialog } from "../dialog-command" -import { useRenderer, type JSX } from "@opentui/solid" +import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid" import * as Editor from "@tui/util/editor" import { useExit } from "../../context/exit" import * as Clipboard from "../../util/clipboard" @@ -94,6 +95,7 @@ export function Prompt(props: PromptProps) { const local = useLocal() const args = useArgs() const sdk = useSDK() + const editor = useEditorContext() const route = useRoute() const project = useProject() const sync = useSync() @@ -104,11 +106,34 @@ export function Prompt(props: PromptProps) { const stash = usePromptStash() const command = useCommandDialog() const renderer = useRenderer() + const dimensions = useTerminalDimensions() const { theme, syntax } = useTheme() const kv = useKV() const animationsEnabled = createMemo(() => kv.get("animations_enabled", true)) const list = createMemo(() => props.placeholders?.normal ?? []) const shell = createMemo(() => props.placeholders?.shell ?? []) + const editorPath = createMemo(() => editor.selection()?.filePath) + const editorSelectionLabel = createMemo(() => { + const selection = editor.selection()?.selection + if (!selection) return + if (selection.start.line === selection.end.line && selection.start.character === selection.end.character) return + if (selection.start.line === selection.end.line) return `#${selection.start.line}` + return `#${selection.start.line}-${selection.end.line}` + }) + const editorFileLabel = createMemo(() => { + const value = editorPath() + if (!value) return + const filename = path.basename(value) + const file = /^index\.[^./]+$/.test(filename) + ? [path.basename(path.dirname(value)), filename].filter(Boolean).join("/") + : filename + return `${file.split(path.sep).join("/")}${editorSelectionLabel() ?? ""}` + }) + const editorFileLabelDisplay = createMemo(() => { + const file = editorFileLabel() + if (!file) return + return Locale.truncateMiddle(file, Math.max(12, Math.min(48, Math.floor(dimensions().width / 3)))) + }) const [auto, setAuto] = createSignal() const currentProviderLabel = createMemo(() => local.model.parsed().provider) const hasRightContent = createMemo(() => Boolean(props.right)) @@ -721,6 +746,27 @@ export function Prompt(props: PromptProps) { // Capture mode before it gets reset const currentMode = store.mode const variant = local.model.variant.current() + const editorSelection = editor.selection() + const editorParts = editorSelection + ? [ + { + id: PartID.ascending(), + type: "text" as const, + text: (() => { + const start = editorSelection.selection.start + const end = editorSelection.selection.end + if (start.line === end.line && start.character === end.character) { + return `Note: The user opened the file "${editorSelection.filePath}".` + } + if (start.line === end.line) { + return `Note: The user selected line ${start.line} from "${editorSelection.filePath}": ${editorSelection.text}` + } + return `Note: The user selected lines ${start.line} to ${end.line} from "${editorSelection.filePath}": ${editorSelection.text}` + })(), + synthetic: true, + }, + ] + : [] if (store.mode === "shell") { void sdk.client.session.shell({ @@ -773,6 +819,7 @@ export function Prompt(props: PromptProps) { model: selectedModel, variant, parts: [ + ...editorParts, { id: PartID.ascending(), type: "text", @@ -1332,6 +1379,7 @@ export function Prompt(props: PromptProps) { + {(file) => {file()}} diff --git a/packages/opencode/src/cli/cmd/tui/context/editor.ts b/packages/opencode/src/cli/cmd/tui/context/editor.ts new file mode 100644 index 000000000..663b0f0fa --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/context/editor.ts @@ -0,0 +1,318 @@ +import { readdirSync, readFileSync, statSync } from "node:fs" +import os from "node:os" +import path from "node:path" +import { onCleanup, onMount } from "solid-js" +import { createStore } from "solid-js/store" +import z from "zod" +import { createSimpleContext } from "./helper" + +const MCP_PROTOCOL_VERSION = "2025-11-25" + +const JsonRpcMessageSchema = z.object({ + id: z.union([z.number(), z.string(), z.null()]).optional(), + method: z.string().optional(), + params: z.unknown().optional(), + result: z.unknown().optional(), + error: z + .object({ + code: z.number().optional(), + message: z.string().optional(), + }) + .optional(), +}) + +const PositionSchema = z.object({ + line: z.number(), + character: z.number(), +}) + +const EditorSelectionSchema = z.object({ + text: z.string(), + filePath: z.string(), + selection: z.object({ + start: PositionSchema, + end: PositionSchema, + }), +}) + +const EditorMentionSchema = z.object({ + filePath: z.string(), + lineStart: z.number(), + lineEnd: z.number(), +}) + +const EditorServerInfoSchema = z.object({ + protocolVersion: z.string().optional(), + serverInfo: z + .object({ + name: z.string().optional(), + version: z.string().optional(), + }) + .optional(), +}) + +type JsonRpcMessage = z.infer +export type EditorSelection = z.infer +export type EditorMention = z.infer +type EditorServerInfo = z.infer + +type EditorConnection = { + url: string + authToken?: string + source: string +} + +type EditorLockFile = { + port: number + authToken?: string + transport?: string + workspaceFolders: string[] + mtimeMs: number +} + +export const { use: useEditorContext, provider: EditorContextProvider } = createSimpleContext({ + name: "EditorContext", + init: () => { + const mentionListeners = new Set<(mention: EditorMention) => void>() + const [store, setStore] = createStore<{ + status: "disabled" | "connecting" | "connected" + selection: EditorSelection | undefined + server: EditorServerInfo | undefined + }>({ + status: "disabled", + selection: undefined, + server: undefined, + }) + + onMount(() => { + let socket: WebSocket | undefined + let closed = false + let reconnect: ReturnType | undefined + let attempt = 0 + let requestID = 0 + const pending = new Map() + + const send = (payload: JsonRpcMessage) => { + if (!socket || socket.readyState !== WebSocket.OPEN) return + socket.send(JSON.stringify({ jsonrpc: "2.0", ...payload })) + } + + const request = (method: string, params?: unknown) => { + requestID += 1 + pending.set(requestID, method) + send({ id: requestID, method, params }) + } + + const scheduleReconnect = (delay: number) => { + if (closed) return + if (reconnect) clearTimeout(reconnect) + reconnect = setTimeout(connect, delay) + } + + const connect = () => { + if (closed) return + + const connection = resolveEditorConnection() + if (!connection) { + setStore("status", "disabled") + scheduleReconnect(1000) + return + } + + setStore("status", "connecting") + const current = openEditorSocket(connection) + socket = current + + current.addEventListener("open", () => { + if (socket !== current) { + current.close() + return + } + + attempt = 0 + setStore("status", "connected") + request("initialize", { + protocolVersion: MCP_PROTOCOL_VERSION, + capabilities: {}, + clientInfo: { name: "opencode", version: "0.0.0" }, + }) + }) + + current.addEventListener("message", (event) => { + const message = parseMessage(event.data) + if (!message) return + + const selection = message.method === "selection_changed" ? EditorSelectionSchema.safeParse(message.params) : undefined + if (selection?.success) { + setStore("selection", selection.data) + return + } + + const mention = message.method === "at_mentioned" ? EditorMentionSchema.safeParse(message.params) : undefined + if (mention?.success) { + mentionListeners.forEach((listener) => listener(mention.data)) + return + } + + if (typeof message.id !== "number") return + + const method = pending.get(message.id) + if (!method) return + + pending.delete(message.id) + if (message.error) return + + const initialize = method === "initialize" ? EditorServerInfoSchema.safeParse(message.result) : undefined + if (initialize?.success) { + setStore("server", initialize.data) + send({ method: "notifications/initialized" }) + return + } + }) + + current.addEventListener("close", () => { + if (socket !== current) return + + socket = undefined + pending.clear() + if (closed) return + + setStore("status", "connecting") + attempt += 1 + const delay = Math.min(1000 * 2 ** (attempt - 1), 30000) + scheduleReconnect(delay) + }) + } + + scheduleReconnect(0) + + onCleanup(() => { + closed = true + if (reconnect) clearTimeout(reconnect) + socket?.close() + }) + }) + + return { + enabled() { + return Boolean(resolveEditorConnection()) + }, + connected() { + return store.status === "connected" + }, + selection() { + return store.selection + }, + onMention(listener: (mention: EditorMention) => void) { + mentionListeners.add(listener) + return () => mentionListeners.delete(listener) + }, + server() { + return store.server + }, + } + }, +}) + +function parsePort(value: string | undefined) { + if (!value) return + + const parsed = Number.parseInt(value, 10) + if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) return + return parsed +} + +function resolveEditorConnection(): EditorConnection | undefined { + const lock = resolveEditorLockFile() + if (lock) { + return { + url: `ws://127.0.0.1:${lock.port}`, + authToken: lock.authToken, + source: `lock:${lock.port}`, + } + } + + const port = parsePort(process.env.CLAUDE_CODE_SSE_PORT || process.env.OPENCODE_EDITOR_SSE_PORT) + if (!port) return + return { + url: `ws://127.0.0.1:${port}`, + source: `env:${port}`, + } +} + +function resolveEditorLockFile() { + const directory = path.join(os.homedir(), ".claude", "ide") + let entries: string[] + + try { + entries = readdirSync(directory) + } catch { + return + } + + const cwd = process.cwd() + const locks = entries + .filter((entry) => entry.endsWith(".lock")) + .map((entry) => readEditorLockFile(path.join(directory, entry))) + .filter((entry): entry is EditorLockFile => Boolean(entry)) + .sort((left, right) => scoreEditorLock(right, cwd) - scoreEditorLock(left, cwd)) + + return locks[0] +} + +function readEditorLockFile(filePath: string): EditorLockFile | undefined { + const port = parsePort(path.basename(filePath, ".lock")) + if (!port) return + + try { + const parsed = JSON.parse(readFileSync(filePath, "utf-8")) as unknown + if (!isRecord(parsed)) return + if (parsed.transport !== undefined && parsed.transport !== "ws") return + + return { + port, + authToken: typeof parsed.authToken === "string" ? parsed.authToken : undefined, + transport: typeof parsed.transport === "string" ? parsed.transport : undefined, + workspaceFolders: Array.isArray(parsed.workspaceFolders) + ? parsed.workspaceFolders.filter((value): value is string => typeof value === "string") + : [], + mtimeMs: statSync(filePath).mtimeMs, + } + } catch { + return + } +} + +function scoreEditorLock(lock: EditorLockFile, cwd: string) { + const workspaceMatch = lock.workspaceFolders.some((folder) => pathContains(folder, cwd)) ? 1 : 0 + return workspaceMatch * 1_000_000_000_000 + lock.mtimeMs +} + +function pathContains(parent: string, child: string) { + const relative = path.relative(path.resolve(parent), path.resolve(child)) + return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)) +} + +function openEditorSocket(connection: EditorConnection) { + if (!connection.authToken) return new WebSocket(connection.url) + + return new WebSocket(connection.url, { + headers: { + "x-claude-code-ide-authorization": connection.authToken, + }, + } as any) +} + +function parseMessage(value: unknown) { + if (typeof value !== "string") return + + try { + return JsonRpcMessageSchema.parse(JSON.parse(value)) + } catch { + return + } +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value) +} From 4c3e65c87728b04888ccbf3879aca13a3857bb08 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 20:20:26 +0000 Subject: [PATCH 095/426] chore: generate --- packages/opencode/src/cli/cmd/tui/context/editor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/editor.ts b/packages/opencode/src/cli/cmd/tui/context/editor.ts index 663b0f0fa..4e6c97f6e 100644 --- a/packages/opencode/src/cli/cmd/tui/context/editor.ts +++ b/packages/opencode/src/cli/cmd/tui/context/editor.ts @@ -142,7 +142,8 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create const message = parseMessage(event.data) if (!message) return - const selection = message.method === "selection_changed" ? EditorSelectionSchema.safeParse(message.params) : undefined + const selection = + message.method === "selection_changed" ? EditorSelectionSchema.safeParse(message.params) : undefined if (selection?.success) { setStore("selection", selection.data) return From 814e83ffecfddeb60cc88bba03b0c00629eecf5e Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 16:23:45 -0400 Subject: [PATCH 096/426] docs: update effect schema migration tracker (#24054) --- packages/opencode/specs/effect/schema.md | 50 ++++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 0d451f4a9..04f994e31 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -254,8 +254,8 @@ Working rule for this cluster: 5. Errors and event payloads last - `NamedError.create(...)` shapes can stay temporarily if converting them to `Schema.TaggedErrorClass` would force unrelated churn - - `SyncEvent.define(...)` and `BusEvent.define(...)` payloads can keep using - derived `.zod` until the sync/bus layers are migrated + - `SyncEvent.define(...)` and `BusEvent.define(...)` payloads can use + derived `.zod` at remaining zod-based HTTP/OpenAPI boundaries Possible later tightening after the Schema-first migration is stable: @@ -284,25 +284,25 @@ Each tool declares its parameters via a zod schema. Tools are consumed by both the in-process runtime and the AI SDK's tool-calling layer, so the emitted JSON Schema must stay byte-identical. -- [ ] `src/tool/apply_patch.ts` -- [ ] `src/tool/bash.ts` -- [ ] `src/tool/codesearch.ts` -- [ ] `src/tool/edit.ts` -- [ ] `src/tool/glob.ts` -- [ ] `src/tool/grep.ts` -- [ ] `src/tool/invalid.ts` -- [ ] `src/tool/lsp.ts` -- [ ] `src/tool/plan.ts` -- [ ] `src/tool/question.ts` -- [ ] `src/tool/read.ts` -- [ ] `src/tool/registry.ts` -- [ ] `src/tool/skill.ts` -- [ ] `src/tool/task.ts` -- [ ] `src/tool/todo.ts` -- [ ] `src/tool/tool.ts` -- [ ] `src/tool/webfetch.ts` -- [ ] `src/tool/websearch.ts` -- [ ] `src/tool/write.ts` +- [x] `src/tool/apply_patch.ts` +- [x] `src/tool/bash.ts` +- [x] `src/tool/codesearch.ts` +- [x] `src/tool/edit.ts` +- [x] `src/tool/glob.ts` +- [x] `src/tool/grep.ts` +- [x] `src/tool/invalid.ts` +- [x] `src/tool/lsp.ts` +- [x] `src/tool/plan.ts` +- [x] `src/tool/question.ts` +- [x] `src/tool/read.ts` +- [x] `src/tool/registry.ts` +- [x] `src/tool/skill.ts` +- [x] `src/tool/task.ts` +- [x] `src/tool/todo.ts` +- [x] `src/tool/tool.ts` +- [x] `src/tool/webfetch.ts` +- [x] `src/tool/websearch.ts` +- [x] `src/tool/write.ts` ### HTTP route boundaries @@ -313,8 +313,8 @@ which means touching them is largely mechanical once the domain side is done. - [ ] `src/server/error.ts` -- [ ] `src/server/event.ts` -- [ ] `src/server/projectors.ts` +- [x] `src/server/event.ts` +- [x] `src/server/projectors.ts` - [ ] `src/server/routes/control/index.ts` - [ ] `src/server/routes/control/workspace.ts` - [ ] `src/server/routes/global.ts` @@ -346,7 +346,7 @@ piecewise. - [ ] `src/acp/agent.ts` - [ ] `src/agent/agent.ts` -- [ ] `src/bus/bus-event.ts` +- [x] `src/bus/bus-event.ts` - [ ] `src/bus/index.ts` - [ ] `src/cli/cmd/tui/config/tui-migrate.ts` - [ ] `src/cli/cmd/tui/config/tui-schema.ts` @@ -376,7 +376,7 @@ piecewise. - [ ] `src/snapshot/index.ts` - [ ] `src/storage/db.ts` - [ ] `src/storage/storage.ts` -- [x] `src/sync/index.ts` — public API (`SyncEvent.define`) is Schema-first; still stores derived zod `schema`/`properties` on each `Definition` as a compat bridge for `BusEvent` until `bus/bus-event.ts` migrates +- [x] `src/sync/index.ts` — public API (`SyncEvent.define`) is Schema-first; `payloads()` still derives zod for the remaining HTTP/OpenAPI boundary - [ ] `src/util/fn.ts` - [ ] `src/util/log.ts` - [ ] `src/util/update-schema.ts` From 31d01d404afd4f82628b3f388d5c73debf250ffd Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 16:48:56 -0400 Subject: [PATCH 097/426] refactor(control-plane): migrate workspace DTO schemas (#24056) --- packages/opencode/specs/effect/schema.md | 6 +-- .../src/control-plane/adaptors/index.ts | 8 +--- .../src/control-plane/adaptors/worktree.ts | 20 +++++----- packages/opencode/src/control-plane/types.ts | 31 +++++++++----- .../opencode/src/control-plane/workspace.ts | 40 +++++++++---------- .../src/server/routes/control/workspace.ts | 27 +++++-------- 6 files changed, 67 insertions(+), 65 deletions(-) diff --git a/packages/opencode/specs/effect/schema.md b/packages/opencode/specs/effect/schema.md index 04f994e31..0319df4a0 100644 --- a/packages/opencode/specs/effect/schema.md +++ b/packages/opencode/specs/effect/schema.md @@ -354,9 +354,9 @@ piecewise. - [ ] `src/cli/cmd/tui/event.ts` - [ ] `src/cli/ui.ts` - [ ] `src/command/index.ts` -- [ ] `src/control-plane/adaptors/worktree.ts` -- [ ] `src/control-plane/types.ts` -- [ ] `src/control-plane/workspace.ts` +- [x] `src/control-plane/adaptors/worktree.ts` +- [x] `src/control-plane/types.ts` +- [x] `src/control-plane/workspace.ts` - [ ] `src/file/index.ts` - [ ] `src/file/ripgrep.ts` - [ ] `src/file/watcher.ts` diff --git a/packages/opencode/src/control-plane/adaptors/index.ts b/packages/opencode/src/control-plane/adaptors/index.ts index 291e392ea..651d09cc2 100644 --- a/packages/opencode/src/control-plane/adaptors/index.ts +++ b/packages/opencode/src/control-plane/adaptors/index.ts @@ -1,12 +1,6 @@ import { lazy } from "@/util/lazy" import type { ProjectID } from "@/project/schema" -import type { WorkspaceAdaptor } from "../types" - -export type WorkspaceAdaptorEntry = { - type: string - name: string - description: string -} +import type { WorkspaceAdaptor, WorkspaceAdaptorEntry } from "../types" const BUILTIN: Record Promise> = { worktree: lazy(async () => (await import("./worktree")).WorktreeAdaptor), diff --git a/packages/opencode/src/control-plane/adaptors/worktree.ts b/packages/opencode/src/control-plane/adaptors/worktree.ts index 2bfb7deba..8d421b9a3 100644 --- a/packages/opencode/src/control-plane/adaptors/worktree.ts +++ b/packages/opencode/src/control-plane/adaptors/worktree.ts @@ -1,13 +1,15 @@ -import z from "zod" +import { Schema } from "effect" import { AppRuntime } from "@/effect/app-runtime" import { Worktree } from "@/worktree" import { type WorkspaceAdaptor, WorkspaceInfo } from "../types" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" -const WorktreeConfig = z.object({ - name: WorkspaceInfo.shape.name, - branch: WorkspaceInfo.shape.branch.unwrap(), - directory: WorkspaceInfo.shape.directory.unwrap(), -}) +const WorktreeConfig = Schema.Struct({ + name: WorkspaceInfo.fields.name, + branch: Schema.String, + directory: Schema.String, +}).pipe(withStatics((s) => ({ zod: zod(s) }))) export const WorktreeAdaptor: WorkspaceAdaptor = { name: "Worktree", @@ -22,7 +24,7 @@ export const WorktreeAdaptor: WorkspaceAdaptor = { } }, async create(info) { - const config = WorktreeConfig.parse(info) + const config = WorktreeConfig.zod.parse(info) await AppRuntime.runPromise( Worktree.Service.use((svc) => svc.createFromInfo({ @@ -34,11 +36,11 @@ export const WorktreeAdaptor: WorkspaceAdaptor = { ) }, async remove(info) { - const config = WorktreeConfig.parse(info) + const config = WorktreeConfig.zod.parse(info) await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove({ directory: config.directory }))) }, target(info) { - const config = WorktreeConfig.parse(info) + const config = WorktreeConfig.zod.parse(info) return { type: "local", directory: config.directory, diff --git a/packages/opencode/src/control-plane/types.ts b/packages/opencode/src/control-plane/types.ts index 07acd5ce5..af16c0490 100644 --- a/packages/opencode/src/control-plane/types.ts +++ b/packages/opencode/src/control-plane/types.ts @@ -1,17 +1,28 @@ -import z from "zod" +import { Schema } from "effect" import { ProjectID } from "@/project/schema" import { WorkspaceID } from "./schema" +import { zod } from "@/util/effect-zod" +import { type DeepMutable, withStatics } from "@/util/schema" -export const WorkspaceInfo = z.object({ - id: WorkspaceID.zod, - type: z.string(), - name: z.string(), - branch: z.string().nullable(), - directory: z.string().nullable(), - extra: z.unknown().nullable(), - projectID: ProjectID.zod, +export const WorkspaceInfo = Schema.Struct({ + id: WorkspaceID, + type: Schema.String, + name: Schema.String, + branch: Schema.NullOr(Schema.String), + directory: Schema.NullOr(Schema.String), + extra: Schema.NullOr(Schema.Unknown), + projectID: ProjectID, }) -export type WorkspaceInfo = z.infer + .annotate({ identifier: "Workspace" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type WorkspaceInfo = DeepMutable> + +export const WorkspaceAdaptorEntry = Schema.Struct({ + type: Schema.String, + name: Schema.String, + description: Schema.String, +}).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type WorkspaceAdaptorEntry = Schema.Schema.Type export type Target = | { diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index 8519cb06c..107f2d990 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -1,4 +1,3 @@ -import z from "zod" import { Schema } from "effect" import { setTimeout as sleep } from "node:timers/promises" import { fn } from "@/util/fn" @@ -16,7 +15,7 @@ import { ProjectID } from "@/project/schema" import { Slug } from "@opencode-ai/shared/util/slug" import { WorkspaceTable } from "./workspace.sql" import { getAdaptor } from "./adaptors" -import { WorkspaceInfo } from "./types" +import { type WorkspaceInfo, WorkspaceInfo as WorkspaceInfoSchema } from "./types" import { WorkspaceID } from "./schema" import { parseSSE } from "./sse" import { Session } from "@/session" @@ -26,12 +25,11 @@ import { errorData } from "@/util/error" import { AppRuntime } from "@/effect/app-runtime" import { waitEvent } from "./util" import { WorkspaceContext } from "./workspace-context" -import { NonNegativeInt } from "@/util/schema" +import { NonNegativeInt, withStatics } from "@/util/schema" +import { zod as effectZod, zodObject } from "@/util/effect-zod" -export const Info = WorkspaceInfo.meta({ - ref: "Workspace", -}) -export type Info = z.infer +export const Info = WorkspaceInfoSchema +export type Info = WorkspaceInfo export const ConnectionStatus = Schema.Struct({ workspaceID: WorkspaceID, @@ -75,15 +73,16 @@ function fromRow(row: typeof WorkspaceTable.$inferSelect): Info { } } -const CreateInput = z.object({ - id: WorkspaceID.zod.optional(), - type: Info.shape.type, - branch: Info.shape.branch, - projectID: ProjectID.zod, - extra: Info.shape.extra, -}) +export const CreateInput = Schema.Struct({ + id: Schema.optional(WorkspaceID), + type: Info.fields.type, + branch: Info.fields.branch, + projectID: ProjectID, + extra: Info.fields.extra, +}).pipe(withStatics((s) => ({ zod: effectZod(s), zodObject: zodObject(s) }))) +export type CreateInput = Schema.Schema.Type -export const create = fn(CreateInput, async (input) => { +export const create = fn(CreateInput.zod, async (input) => { const id = WorkspaceID.ascending(input.id) const adaptor = await getAdaptor(input.projectID, input.type) @@ -139,12 +138,13 @@ export const create = fn(CreateInput, async (input) => { return info }) -const SessionRestoreInput = z.object({ - workspaceID: WorkspaceID.zod, - sessionID: SessionID.zod, -}) +export const SessionRestoreInput = Schema.Struct({ + workspaceID: WorkspaceID, + sessionID: SessionID, +}).pipe(withStatics((s) => ({ zod: effectZod(s), zodObject: zodObject(s) }))) +export type SessionRestoreInput = Schema.Schema.Type -export const sessionRestore = fn(SessionRestoreInput, async (input) => { +export const sessionRestore = fn(SessionRestoreInput.zod, async (input) => { log.info("session restore requested", { workspaceID: input.workspaceID, sessionID: input.sessionID, diff --git a/packages/opencode/src/server/routes/control/workspace.ts b/packages/opencode/src/server/routes/control/workspace.ts index d48c687f3..ffbaa6009 100644 --- a/packages/opencode/src/server/routes/control/workspace.ts +++ b/packages/opencode/src/server/routes/control/workspace.ts @@ -3,6 +3,7 @@ import { describeRoute, resolver, validator } from "hono-openapi" import z from "zod" import { listAdaptors } from "@/control-plane/adaptors" import { Workspace } from "@/control-plane/workspace" +import { WorkspaceAdaptorEntry } from "@/control-plane/types" import { zodObject } from "@/util/effect-zod" import { Instance } from "@/project/instance" import { errors } from "../../error" @@ -26,13 +27,7 @@ export const WorkspaceRoutes = lazy(() => content: { "application/json": { schema: resolver( - z.array( - z.object({ - type: z.string(), - name: z.string(), - description: z.string(), - }), - ), + z.array(zodObject(WorkspaceAdaptorEntry)), ), }, }, @@ -54,7 +49,7 @@ export const WorkspaceRoutes = lazy(() => description: "Workspace created", content: { "application/json": { - schema: resolver(Workspace.Info), + schema: resolver(Workspace.Info.zod), }, }, }, @@ -63,12 +58,12 @@ export const WorkspaceRoutes = lazy(() => }), validator( "json", - Workspace.create.schema.omit({ + Workspace.CreateInput.zodObject.omit({ projectID: true, }), ), async (c) => { - const body = c.req.valid("json") + const body = c.req.valid("json") as Omit const workspace = await Workspace.create({ projectID: Instance.project.id, ...body, @@ -87,7 +82,7 @@ export const WorkspaceRoutes = lazy(() => description: "Workspaces", content: { "application/json": { - schema: resolver(z.array(Workspace.Info)), + schema: resolver(z.array(Workspace.Info.zod)), }, }, }, @@ -130,7 +125,7 @@ export const WorkspaceRoutes = lazy(() => description: "Workspace removed", content: { "application/json": { - schema: resolver(Workspace.Info.optional()), + schema: resolver(Workspace.Info.zod.optional()), }, }, }, @@ -140,7 +135,7 @@ export const WorkspaceRoutes = lazy(() => validator( "param", z.object({ - id: Workspace.Info.shape.id, + id: zodObject(Workspace.Info).shape.id, }), ), async (c) => { @@ -170,11 +165,11 @@ export const WorkspaceRoutes = lazy(() => ...errors(400), }, }), - validator("param", z.object({ id: Workspace.Info.shape.id })), - validator("json", Workspace.sessionRestore.schema.omit({ workspaceID: true })), + validator("param", z.object({ id: zodObject(Workspace.Info).shape.id })), + validator("json", Workspace.SessionRestoreInput.zodObject.omit({ workspaceID: true })), async (c) => { const { id } = c.req.valid("param") - const body = c.req.valid("json") + const body = c.req.valid("json") as Omit log.info("session restore route requested", { workspaceID: id, sessionID: body.sessionID, From a771859362ab61983671ca9a82ec1ccfa34fc689 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 20:50:19 +0000 Subject: [PATCH 098/426] chore: generate --- packages/opencode/src/server/routes/control/workspace.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/opencode/src/server/routes/control/workspace.ts b/packages/opencode/src/server/routes/control/workspace.ts index ffbaa6009..bf5584347 100644 --- a/packages/opencode/src/server/routes/control/workspace.ts +++ b/packages/opencode/src/server/routes/control/workspace.ts @@ -26,9 +26,7 @@ export const WorkspaceRoutes = lazy(() => description: "Workspace adaptors", content: { "application/json": { - schema: resolver( - z.array(zodObject(WorkspaceAdaptorEntry)), - ), + schema: resolver(z.array(zodObject(WorkspaceAdaptorEntry))), }, }, }, From 87321942fee25786448f02284ebeaee1caf4e86c Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:08:46 -0400 Subject: [PATCH 099/426] chore: update copilot readme to symlink to an agents md to prevent dumbass agents from touching these files (#24057) --- packages/opencode/src/provider/sdk/copilot/AGENTS.md | 1 + packages/opencode/src/provider/sdk/copilot/README.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 120000 packages/opencode/src/provider/sdk/copilot/AGENTS.md diff --git a/packages/opencode/src/provider/sdk/copilot/AGENTS.md b/packages/opencode/src/provider/sdk/copilot/AGENTS.md new file mode 120000 index 000000000..42061c01a --- /dev/null +++ b/packages/opencode/src/provider/sdk/copilot/AGENTS.md @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/packages/opencode/src/provider/sdk/copilot/README.md b/packages/opencode/src/provider/sdk/copilot/README.md index 8ce03d614..d1051a4da 100644 --- a/packages/opencode/src/provider/sdk/copilot/README.md +++ b/packages/opencode/src/provider/sdk/copilot/README.md @@ -1,5 +1,5 @@ This is a temporary package used primarily for GitHub Copilot compatibility. -Avoid making changes to these files unless you only want to affect the Copilot provider. +These DO NOT apply for openai-compatible providers or majority of providers supporting completions/responses apis. THIS IS ONLY FOR GITHUB COPILOT!!! -Also, this should ONLY be used for the Copilot provider. +Avoid making edits to these files From 334ab4707c809172e77619ae7d6b22c5577c7238 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:31:43 -0400 Subject: [PATCH 100/426] fix: account for additional openai retry case (#24063) --- packages/opencode/src/provider/error.ts | 12 ++++++++-- .../opencode/test/session/message-v2.test.ts | 24 +++++++++++++++++++ packages/opencode/test/session/retry.test.ts | 22 +++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/provider/error.ts b/packages/opencode/src/provider/error.ts index a2409559f..a4f629caf 100644 --- a/packages/opencode/src/provider/error.ts +++ b/packages/opencode/src/provider/error.ts @@ -111,12 +111,13 @@ export type ParsedStreamError = | { type: "api_error" message: string - isRetryable: false + isRetryable: boolean responseBody: string } export function parseStreamError(input: unknown): ParsedStreamError | undefined { - const body = json(input) + const raw = json(input) + const body = typeof raw?.message === "string" ? (json(raw.message) ?? raw) : raw if (!body) return const responseBody = JSON.stringify(body) @@ -150,6 +151,13 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined isRetryable: false, responseBody, } + case "server_error": + return { + type: "api_error", + message: typeof body?.error?.message === "string" ? body?.error?.message : "Server error.", + isRetryable: true, + responseBody, + } } } diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index 231d58c21..abada013d 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -1075,6 +1075,30 @@ describe("session.message-v2.fromError", () => { }) }) + test("serializes OpenAI response server_error stream chunks as retryable APIError", () => { + const body = { + type: "error", + sequence_number: 2, + error: { + type: "server_error", + code: "server_error", + message: + "An error occurred while processing your request. You can retry your request, or contact us through our help center at help.openai.com if the error persists. Please include the request ID req_77eccd008d984bf6bf82d1b2c2b68715 in your message.", + param: null, + }, + } + const result = MessageV2.fromError({ message: JSON.stringify(body) }, { providerID }) + + expect(result).toStrictEqual({ + name: "APIError", + data: { + message: body.error.message, + isRetryable: true, + responseBody: JSON.stringify(body), + }, + }) + }) + test("detects context overflow from APICallError provider messages", () => { const cases = [ "prompt is too long: 213462 tokens > 200000 maximum", diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index ade264786..6ca8775f3 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -294,4 +294,26 @@ describe("session.message-v2.fromError", () => { const result = MessageV2.fromError(error, { providerID: ProviderID.make("openai") }) as MessageV2.APIError expect(result.data.isRetryable).toBe(true) }) + + test("converts OpenAI server_error stream chunks to retryable APIError", () => { + const result = MessageV2.fromError( + { + message: JSON.stringify({ + type: "error", + sequence_number: 2, + error: { + type: "server_error", + code: "server_error", + message: "An error occurred while processing your request.", + param: null, + }, + }), + }, + { providerID: ProviderID.make("openai") }, + ) + + expect(MessageV2.APIError.isInstance(result)).toBe(true) + expect((result as MessageV2.APIError).data.isRetryable).toBe(true) + expect(SessionRetry.retryable(result)).toBe("An error occurred while processing your request.") + }) }) From e50a688ca309ba4c992fd8e47b5b75a11aef025e Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 17:32:02 -0400 Subject: [PATCH 101/426] feat(httpapi): bridge workspace read endpoints (#24062) --- packages/opencode/specs/effect/http-api.md | 4 +- .../server/routes/instance/httpapi/server.ts | 3 + .../routes/instance/httpapi/workspace.ts | 82 +++++++++++++++++++ packages/opencode/src/server/server.ts | 25 ++++-- .../test/server/httpapi-workspace.test.ts | 55 +++++++++++++ 5 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 packages/opencode/src/server/routes/instance/httpapi/workspace.ts create mode 100644 packages/opencode/test/server/httpapi-workspace.test.ts diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index d882857ba..6c80dc65a 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -409,7 +409,7 @@ Current instance route inventory: - `project` - `bridged` (partial) bridged endpoints: `GET /project`, `GET /project/current` defer git-init mutation first -- `workspace` - `next` +- `workspace` - `bridged` best small reads: `GET /experimental/workspace/adaptor`, `GET /experimental/workspace`, `GET /experimental/workspace/status` defer create/remove mutations first - `file` - `later` @@ -448,7 +448,7 @@ Recommended near-term sequence: - [x] port `config` providers read endpoint - [x] port `project` read endpoints (`GET /project`, `GET /project/current`) - [x] port `GET /config` full read endpoint -- [ ] port `workspace` read endpoints +- [x] port `workspace` read endpoints - [ ] port `file` JSON read endpoints - [ ] decide when to remove the flag and make Effect routes the default diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index d012e2c16..7b131d400 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -14,6 +14,7 @@ import { PermissionApi, permissionHandlers } from "./permission" import { ProjectApi, projectHandlers } from "./project" import { ProviderApi, providerHandlers } from "./provider" import { QuestionApi, questionHandlers } from "./question" +import { WorkspaceApi, workspaceHandlers } from "./workspace" import { memoMap } from "@/effect/memo-map" const Query = Schema.Struct({ @@ -112,6 +113,7 @@ const PermissionSecured = PermissionApi.middleware(Authorization) const ProjectSecured = ProjectApi.middleware(Authorization) const ProviderSecured = ProviderApi.middleware(Authorization) const ConfigSecured = ConfigApi.middleware(Authorization) +const WorkspaceSecured = WorkspaceApi.middleware(Authorization) export const routes = Layer.mergeAll( HttpApiBuilder.layer(ConfigSecured).pipe(Layer.provide(configHandlers)), @@ -119,6 +121,7 @@ export const routes = Layer.mergeAll( HttpApiBuilder.layer(QuestionSecured).pipe(Layer.provide(questionHandlers)), HttpApiBuilder.layer(PermissionSecured).pipe(Layer.provide(permissionHandlers)), HttpApiBuilder.layer(ProviderSecured).pipe(Layer.provide(providerHandlers)), + HttpApiBuilder.layer(WorkspaceSecured).pipe(Layer.provide(workspaceHandlers)), ).pipe( Layer.provide(auth), Layer.provide(normalize), diff --git a/packages/opencode/src/server/routes/instance/httpapi/workspace.ts b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts new file mode 100644 index 000000000..596545073 --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts @@ -0,0 +1,82 @@ +import { listAdaptors } from "@/control-plane/adaptors" +import { Workspace } from "@/control-plane/workspace" +import { WorkspaceAdaptorEntry } from "@/control-plane/types" +import * as InstanceState from "@/effect/instance-state" +import { Effect, Layer, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" + +const root = "/experimental/workspace" +export const WorkspacePaths = { + adaptors: `${root}/adaptor`, + list: root, + status: `${root}/status`, +} as const + +export const WorkspaceApi = HttpApi.make("workspace") + .add( + HttpApiGroup.make("workspace") + .add( + HttpApiEndpoint.get("adaptors", WorkspacePaths.adaptors, { + success: Schema.Array(WorkspaceAdaptorEntry), + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.workspace.adaptor.list", + summary: "List workspace adaptors", + description: "List all available workspace adaptors for the current project.", + }), + ), + HttpApiEndpoint.get("list", WorkspacePaths.list, { + success: Schema.Array(Workspace.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.workspace.list", + summary: "List workspaces", + description: "List all workspaces.", + }), + ), + HttpApiEndpoint.get("status", WorkspacePaths.status, { + success: Schema.Array(Workspace.ConnectionStatus), + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.workspace.status", + summary: "Workspace status", + description: "Get connection status for workspaces in the current project.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "workspace", + description: "Experimental HttpApi workspace routes.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "opencode experimental HttpApi", + version: "0.0.1", + description: "Experimental HttpApi surface for selected instance routes.", + }), + ) + +export const workspaceHandlers = Layer.unwrap( + Effect.gen(function* () { + const adaptors = Effect.fn("WorkspaceHttpApi.adaptors")(function* () { + const ctx = yield* InstanceState.context + return yield* Effect.promise(() => listAdaptors(ctx.project.id)) + }) + + const list = Effect.fn("WorkspaceHttpApi.list")(function* () { + return Workspace.list((yield* InstanceState.context).project) + }) + + const status = Effect.fn("WorkspaceHttpApi.status")(function* () { + const ids = new Set(Workspace.list((yield* InstanceState.context).project).map((item) => item.id)) + return Workspace.status().filter((item) => ids.has(item.workspaceID)) + }) + + return HttpApiBuilder.group(WorkspaceApi, "workspace", (handlers) => + handlers.handle("adaptors", adaptors).handle("list", list).handle("status", status), + ) + }), +) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 8b1f1aee1..d74de559d 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -16,6 +16,9 @@ import { GlobalRoutes } from "./routes/global" import { WorkspaceRouterMiddleware } from "./workspace" import { InstanceMiddleware } from "./routes/instance/middleware" import { WorkspaceRoutes } from "./routes/control/workspace" +import { ExperimentalHttpApiServer } from "./routes/instance/httpapi/server" +import { WorkspacePaths } from "./routes/instance/httpapi/workspace" +import { Context } from "effect" // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 globalThis.AI_SDK_LOG_WARNINGS = false @@ -54,16 +57,24 @@ function create(opts: { cors?: string[] }) { } } + const workspaceApp = new Hono() + const workspaceLegacyApp = new Hono() + .use(InstanceMiddleware()) + .route("/experimental/workspace", WorkspaceRoutes()) + .use(WorkspaceRouterMiddleware(runtime.upgradeWebSocket)) + if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) { + const handler = ExperimentalHttpApiServer.webHandler().handler + const context = Context.empty() as Context.Context + workspaceApp.get(WorkspacePaths.adaptors, (c) => handler(c.req.raw, context)) + workspaceApp.get(WorkspacePaths.list, (c) => handler(c.req.raw, context)) + workspaceApp.get(WorkspacePaths.status, (c) => handler(c.req.raw, context)) + } + workspaceApp.route("/", workspaceLegacyApp) + return { app: app .route("/", ControlPlaneRoutes()) - .route( - "/", - new Hono() - .use(InstanceMiddleware()) - .route("/experimental/workspace", WorkspaceRoutes()) - .use(WorkspaceRouterMiddleware(runtime.upgradeWebSocket)), - ) + .route("/", workspaceApp) .route("/", InstanceRoutes(runtime.upgradeWebSocket)) .route("/", UIRoutes()), runtime, diff --git a/packages/opencode/test/server/httpapi-workspace.test.ts b/packages/opencode/test/server/httpapi-workspace.test.ts new file mode 100644 index 000000000..8256d8330 --- /dev/null +++ b/packages/opencode/test/server/httpapi-workspace.test.ts @@ -0,0 +1,55 @@ +import { afterEach, describe, expect, test } from "bun:test" +import { Context } from "effect" +import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" +import { WorkspacePaths } from "../../src/server/routes/instance/httpapi/workspace" +import { Log } from "../../src/util" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" +import { Instance } from "../../src/project/instance" + +void Log.init({ print: false }) + +const context = Context.empty() as Context.Context + +function request(path: string, directory: string) { + return ExperimentalHttpApiServer.webHandler().handler( + new Request(`http://localhost${path}`, { + headers: { + "x-opencode-directory": directory, + }, + }), + context, + ) +} + +afterEach(async () => { + await Instance.disposeAll() + await resetDatabase() +}) + +describe("workspace HttpApi", () => { + test("serves read endpoints", async () => { + await using tmp = await tmpdir({ git: true }) + + const [adaptors, workspaces, status] = await Promise.all([ + request(WorkspacePaths.adaptors, tmp.path), + request(WorkspacePaths.list, tmp.path), + request(WorkspacePaths.status, tmp.path), + ]) + + expect(adaptors.status).toBe(200) + expect(await adaptors.json()).toEqual([ + { + type: "worktree", + name: "Worktree", + description: "Create a git worktree", + }, + ]) + + expect(workspaces.status).toBe(200) + expect(await workspaces.json()).toEqual([]) + + expect(status.status).toBe(200) + expect(await status.json()).toEqual([]) + }) +}) From f8c6ddd4cb08aab493fcaab69d44c5085083152e Mon Sep 17 00:00:00 2001 From: rahul Date: Thu, 23 Apr 2026 17:43:33 -0400 Subject: [PATCH 102/426] feat(truncate): allow configuring tool output truncation limits (#23770) Co-authored-by: rgs_ramp Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/config/config.ts | 13 ++++ packages/opencode/src/tool/bash.ts | 17 ++--- packages/opencode/src/tool/truncate.ts | 24 +++++-- .../opencode/test/tool/truncation.test.ts | 64 +++++++++++++++++++ 4 files changed, 106 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index eab199232..f1ceb1b4e 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -201,6 +201,19 @@ export const Info = Schema.Struct({ url: Schema.optional(Schema.String).annotate({ description: "Enterprise URL" }), }), ), + tool_output: Schema.optional( + Schema.Struct({ + max_lines: Schema.optional(PositiveInt).annotate({ + description: "Maximum lines of tool output before it is truncated and saved to disk (default: 2000)", + }), + max_bytes: Schema.optional(PositiveInt).annotate({ + description: "Maximum bytes of tool output before it is truncated and saved to disk (default: 51200)", + }), + }), + ).annotate({ + description: + "Thresholds for truncating tool output. When output exceeds either limit, the full text is written to the truncation directory and a preview is returned.", + }), compaction: Schema.optional( Schema.Struct({ auto: Schema.optional(Schema.Boolean).annotate({ diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 2d4d59b1b..0a7e1a6dc 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -416,9 +416,8 @@ export const BashTool = Tool.define( }, ctx: Tool.Context, ) { - const bytes = Truncate.MAX_BYTES - const lines = Truncate.MAX_LINES - const keep = bytes * 2 + const limits = yield* trunc.limits() + const keep = limits.maxBytes * 2 let full = "" let last = "" const list: Chunk[] = [] @@ -458,7 +457,7 @@ export const BashTool = Tool.define( sink?.write(chunk) } else { full += chunk - if (Buffer.byteLength(full, "utf-8") > bytes) { + if (Buffer.byteLength(full, "utf-8") > limits.maxBytes) { return trunc.write(full).pipe( Effect.andThen((next) => Effect.sync(() => { @@ -525,7 +524,7 @@ export const BashTool = Tool.define( } if (aborted) meta.push("User aborted the command") const raw = list.map((item) => item.text).join("") - const end = tail(raw, lines, bytes) + const end = tail(raw, limits.maxLines, limits.maxBytes) if (end.cut) cut = true if (!file && end.cut) { file = yield* trunc.write(raw) @@ -566,7 +565,7 @@ export const BashTool = Tool.define( }) return () => - Effect.sync(() => { + Effect.gen(function* () { const shell = Shell.acceptable() const name = Shell.name(shell) const chain = @@ -575,13 +574,15 @@ export const BashTool = Tool.define( : "If the commands depend on each other and must run sequentially, use a single Bash call with '&&' to chain them together (e.g., `git add . && git commit -m \"message\" && git push`). For instance, if one operation must complete before another starts (like mkdir before cp, Write before Bash for git operations, or git add before git commit), run these operations sequentially instead." log.info("bash tool using shell", { shell }) + const limits = yield* trunc.limits() + return { description: DESCRIPTION.replaceAll("${directory}", Instance.directory) .replaceAll("${os}", process.platform) .replaceAll("${shell}", name) .replaceAll("${chaining}", chain) - .replaceAll("${maxLines}", String(Truncate.MAX_LINES)) - .replaceAll("${maxBytes}", String(Truncate.MAX_BYTES)), + .replaceAll("${maxLines}", String(limits.maxLines)) + .replaceAll("${maxBytes}", String(limits.maxBytes)), parameters: Parameters, execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { diff --git a/packages/opencode/src/tool/truncate.ts b/packages/opencode/src/tool/truncate.ts index d990e7adf..e0d846858 100644 --- a/packages/opencode/src/tool/truncate.ts +++ b/packages/opencode/src/tool/truncate.ts @@ -1,9 +1,10 @@ import { NodePath } from "@effect/platform-node" -import { Cause, Duration, Effect, Layer, Schedule, Context } from "effect" +import { Cause, Duration, Effect, Layer, Option, Schedule, Context } from "effect" import path from "path" import type { Agent } from "../agent/agent" import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { evaluate } from "@/permission/evaluate" +import { Config } from "../config" import { Identifier } from "../id/id" import { Log } from "../util" import { ToolID } from "./schema" @@ -38,6 +39,10 @@ export interface Interface { * to the truncation directory and returns a preview plus a hint to inspect the saved file. */ readonly output: (text: string, options?: Options, agent?: Agent.Info) => Effect.Effect + /** + * Resolved truncation limits: values from `tool_output` in opencode config, or MAX_LINES / MAX_BYTES if unset. + */ + readonly limits: () => Effect.Effect<{ maxLines: number; maxBytes: number }> } export class Service extends Context.Service()("@opencode/Truncate") {} @@ -68,9 +73,20 @@ export const layer = Layer.effect( return file }) + const limits = Effect.fn("Truncate.limits")(function* () { + const configSvc = yield* Effect.serviceOption(Config.Service) + if (Option.isNone(configSvc)) return { maxLines: MAX_LINES, maxBytes: MAX_BYTES } + const cfg = yield* configSvc.value.get().pipe(Effect.catch(() => Effect.succeed(undefined))) + return { + maxLines: cfg?.tool_output?.max_lines ?? MAX_LINES, + maxBytes: cfg?.tool_output?.max_bytes ?? MAX_BYTES, + } + }) + const output = Effect.fn("Truncate.output")(function* (text: string, options: Options = {}, agent?: Agent.Info) { - const maxLines = options.maxLines ?? MAX_LINES - const maxBytes = options.maxBytes ?? MAX_BYTES + const resolved = yield* limits() + const maxLines = options.maxLines ?? resolved.maxLines + const maxBytes = options.maxBytes ?? resolved.maxBytes const direction = options.direction ?? "head" const lines = text.split("\n") const totalBytes = Buffer.byteLength(text, "utf-8") @@ -135,7 +151,7 @@ export const layer = Layer.effect( Effect.forkScoped, ) - return Service.of({ cleanup, write, output }) + return Service.of({ cleanup, write, output, limits }) }), ) diff --git a/packages/opencode/test/tool/truncation.test.ts b/packages/opencode/test/tool/truncation.test.ts index d3cec4cd9..369ad2d58 100644 --- a/packages/opencode/test/tool/truncation.test.ts +++ b/packages/opencode/test/tool/truncation.test.ts @@ -2,6 +2,7 @@ import { describe, test, expect } from "bun:test" import { NodeFileSystem } from "@effect/platform-node" import { Effect, FileSystem, Layer } from "effect" import { Truncate } from "../../src/tool" +import { Config } from "../../src/config" import { Identifier } from "../../src/id/id" import { Process } from "../../src/util" import { Filesystem } from "../../src/util" @@ -14,6 +15,14 @@ const ROOT = path.resolve(import.meta.dir, "..", "..") const it = testEffect(Layer.mergeAll(Truncate.defaultLayer, NodeFileSystem.layer)) +const configuredLayer = (cfg: Config.Info) => + Layer.mergeAll( + Truncate.defaultLayer, + NodeFileSystem.layer, + Layer.mock(Config.Service)({ get: () => Effect.succeed(cfg) }), + ) +const configuredIt = (cfg: Config.Info) => testEffect(configuredLayer(cfg)) + describe("Truncate", () => { describe("output", () => { it.live("truncates large json file by bytes", () => @@ -94,6 +103,61 @@ describe("Truncate", () => { expect(Truncate.MAX_BYTES).toBe(50 * 1024) }) + it.live("limits() falls back to MAX_LINES/MAX_BYTES when Config is not provided", () => + Effect.gen(function* () { + const svc = yield* Truncate.Service + const resolved = yield* svc.limits() + expect(resolved.maxLines).toBe(Truncate.MAX_LINES) + expect(resolved.maxBytes).toBe(Truncate.MAX_BYTES) + }), + ) + + describe("with tool_output config", () => { + const limitsIt = configuredIt({ tool_output: { max_lines: 123, max_bytes: 456 } }) + limitsIt.live("limits() reflects config overrides", () => + Effect.gen(function* () { + const resolved = yield* (yield* Truncate.Service).limits() + expect(resolved.maxLines).toBe(123) + expect(resolved.maxBytes).toBe(456) + }), + ) + + // Huge byte budget isolates line truncation. 100 lines against max_lines: 10 + // proves the configured line limit is what `output()` enforces. + const lineIt = configuredIt({ tool_output: { max_lines: 10, max_bytes: 1024 * 1024 } }) + lineIt.live("output() truncates to configured max_lines", () => + Effect.gen(function* () { + const content = Array.from({ length: 100 }, (_, i) => `line${i}`).join("\n") + const result = yield* (yield* Truncate.Service).output(content) + expect(result.truncated).toBe(true) + expect(result.content).toContain("...90 lines truncated...") + }), + ) + + // Huge line budget isolates byte truncation. + const byteIt = configuredIt({ tool_output: { max_lines: 1_000_000, max_bytes: 100 } }) + byteIt.live("output() truncates to configured max_bytes", () => + Effect.gen(function* () { + const content = "a".repeat(1000) + const result = yield* (yield* Truncate.Service).output(content) + expect(result.truncated).toBe(true) + expect(result.content).toContain("bytes truncated...") + }), + ) + + const overrideIt = configuredIt({ tool_output: { max_lines: 10, max_bytes: 100 } }) + overrideIt.live("per-call options still override config", () => + Effect.gen(function* () { + const content = Array.from({ length: 50 }, (_, i) => `line${i}`).join("\n") + const result = yield* (yield* Truncate.Service).output(content, { + maxLines: 1000, + maxBytes: 1024 * 1024, + }) + expect(result.truncated).toBe(false) + }), + ) + }) + it.live("large single-line file truncates with byte message", () => Effect.gen(function* () { const svc = yield* Truncate.Service From 5c5069b6227ce6c4cbc1b6daca65da69daf43f84 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 23 Apr 2026 21:44:44 +0000 Subject: [PATCH 103/426] chore: generate --- packages/sdk/js/src/v2/gen/types.gen.ts | 13 +++++++++++++ packages/sdk/openapi.json | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index d28ce2579..40e661b46 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1633,6 +1633,19 @@ export type Config = { */ url?: string } + /** + * Thresholds for truncating tool output. When output exceeds either limit, the full text is written to the truncation directory and a preview is returned. + */ + tool_output?: { + /** + * Maximum lines of tool output before it is truncated and saved to disk (default: 2000) + */ + max_lines?: number + /** + * Maximum bytes of tool output before it is truncated and saved to disk (default: 51200) + */ + max_bytes?: number + } compaction?: { /** * Enable automatic compaction when context is full (default: true) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 0c3d18972..cd7b381d8 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -11822,6 +11822,24 @@ } } }, + "tool_output": { + "description": "Thresholds for truncating tool output. When output exceeds either limit, the full text is written to the truncation directory and a preview is returned.", + "type": "object", + "properties": { + "max_lines": { + "description": "Maximum lines of tool output before it is truncated and saved to disk (default: 2000)", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "max_bytes": { + "description": "Maximum bytes of tool output before it is truncated and saved to disk (default: 51200)", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + } + }, "compaction": { "type": "object", "properties": { From 3bfe6a1ef6cf41bc7f05339d63ab8d6032c6e8e1 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Fri, 24 Apr 2026 12:50:34 +0800 Subject: [PATCH 104/426] ci: add platform-specific bun install flags (#23822) --- .github/actions/setup-bun/action.yml | 9 +++++++-- .github/workflows/publish.yml | 8 ++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index d1e3bfc25..9859174a2 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -1,5 +1,10 @@ name: "Setup Bun" description: "Setup Bun with caching and install dependencies" +inputs: + install-flags: + description: "Additional flags to pass to 'bun install'" + required: false + default: "" runs: using: "composite" steps: @@ -46,8 +51,8 @@ runs: # e.g. ./patches/ for standard-openapi # https://github.com/oven-sh/bun/issues/28147 if [ "$RUNNER_OS" = "Windows" ]; then - bun install --linker hoisted + bun install --linker hoisted ${{ inputs.install-flags }} else - bun install + bun install ${{ inputs.install-flags }} fi shell: bash diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index af008f6b1..6cb6af0a8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -402,12 +402,14 @@ jobs: fail-fast: false matrix: settings: - - host: macos-latest + - host: macos-26-intel target: x86_64-apple-darwin platform_flag: --mac --x64 - - host: macos-latest + bun_install_flags: --os=darwin --cpu=x64 + - host: macos-26 target: aarch64-apple-darwin platform_flag: --mac --arm64 + bun_install_flags: --os=darwin --cpu=arm64 # github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain - host: "windows-2025" target: aarch64-pc-windows-msvc @@ -437,6 +439,8 @@ jobs: run: echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8 - uses: ./.github/actions/setup-bun + with: + install-flags: ${{ matrix.settings.bun_install_flags }} - name: Azure login if: runner.os == 'Windows' From 2e156b8990a1e72cfb231eadafe76e4e60c096ea Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Fri, 24 Apr 2026 13:27:36 +0800 Subject: [PATCH 105/426] fix(desktop): avoid relaunching without installing updates (#23806) --- .../app/src/components/settings-general.tsx | 5 ++--- packages/app/src/context/platform.tsx | 6 +++--- packages/app/src/pages/error.tsx | 5 ++--- packages/app/src/pages/layout.tsx | 5 ++--- packages/desktop-electron/src/main/index.ts | 21 +++++++++++++++---- .../desktop-electron/src/renderer/index.tsx | 2 +- packages/desktop/src/index.tsx | 9 ++++++-- 7 files changed, 34 insertions(+), 19 deletions(-) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 13651aac0..86b8c317c 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -129,13 +129,12 @@ export const SettingsGeneral: Component = () => { } const actions = - platform.update && platform.restart + platform.updateAndRestart ? [ { label: language.t("toast.update.action.installRestart"), onClick: async () => { - await platform.update!() - await platform.restart!() + await platform.updateAndRestart!() }, }, { diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx index 3bdc46391..fd89bf51b 100644 --- a/packages/app/src/context/platform.tsx +++ b/packages/app/src/context/platform.tsx @@ -49,11 +49,11 @@ export type Platform = { /** Storage mechanism, defaults to localStorage */ storage?: (name?: string) => SyncStorage | AsyncStorage - /** Check for updates (Tauri only) */ + /** Check for a downloadable desktop update */ checkUpdate?(): Promise - /** Install updates (Tauri only) */ - update?(): Promise + /** Install the downloaded update using the platform restart flow */ + updateAndRestart?(): Promise /** Fetch override */ fetch?: typeof fetch diff --git a/packages/app/src/pages/error.tsx b/packages/app/src/pages/error.tsx index 11284b3d2..ba0045ec9 100644 --- a/packages/app/src/pages/error.tsx +++ b/packages/app/src/pages/error.tsx @@ -244,10 +244,9 @@ export const ErrorPage: Component = (props) => { } async function installUpdate() { - if (!platform.update || !platform.restart) return + if (!platform.updateAndRestart) return await platform - .update() - .then(() => platform.restart!()) + .updateAndRestart() .then(() => setStore("actionError", undefined)) .catch((err) => { setStore("actionError", formatError(err, language.t)) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 3d3bd5e97..ac5cf104a 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -366,7 +366,7 @@ export default function Layout(props: ParentProps) { const useUpdatePolling = () => onMount(() => { - if (!platform.checkUpdate || !platform.update || !platform.restart) return + if (!platform.checkUpdate || !platform.updateAndRestart) return let toastId: number | undefined let interval: ReturnType | undefined @@ -384,8 +384,7 @@ export default function Layout(props: ParentProps) { { label: language.t("toast.update.action.installRestart"), onClick: async () => { - await platform.update!() - await platform.restart!() + await platform.updateAndRestart!() }, }, { diff --git a/packages/desktop-electron/src/main/index.ts b/packages/desktop-electron/src/main/index.ts index ae9f58118..c9f16606a 100644 --- a/packages/desktop-electron/src/main/index.ts +++ b/packages/desktop-electron/src/main/index.ts @@ -337,11 +337,16 @@ function setupAutoUpdater() { }) } -let updateReady = false +let downloadedUpdateVersion: string | undefined async function checkUpdate() { if (!UPDATER_ENABLED) return { updateAvailable: false } - updateReady = false + if (downloadedUpdateVersion) { + logger.log("returning cached downloaded update", { + version: downloadedUpdateVersion, + }) + return { updateAvailable: true, version: downloadedUpdateVersion } + } logger.log("checking for updates", { currentVersion: app.getVersion(), channel: autoUpdater.channel, @@ -367,7 +372,7 @@ async function checkUpdate() { logger.log("update available", { version }) await autoUpdater.downloadUpdate() logger.log("update download completed", { version }) - updateReady = true + downloadedUpdateVersion = version return { updateAvailable: true, version } } catch (error) { logger.error("update check failed", error) @@ -376,7 +381,15 @@ async function checkUpdate() { } async function installUpdate() { - if (!updateReady) return + if (!downloadedUpdateVersion) { + logger.log("install update skipped", { + reason: "no downloaded update ready", + }) + return + } + logger.log("installing downloaded update", { + version: downloadedUpdateVersion, + }) killSidecar() autoUpdater.quitAndInstall() } diff --git a/packages/desktop-electron/src/renderer/index.tsx b/packages/desktop-electron/src/renderer/index.tsx index 56fe9fa51..91ea1ae07 100644 --- a/packages/desktop-electron/src/renderer/index.tsx +++ b/packages/desktop-electron/src/renderer/index.tsx @@ -170,7 +170,7 @@ const createPlatform = (): Platform => { return window.api.checkUpdate() }, - update: async () => { + updateAndRestart: async () => { const config = await window.api.getWindowConfig().catch(() => ({ updaterEnabled: false })) if (!config.updaterEnabled) return await window.api.installUpdate() diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index d6a0ad74f..a760cb409 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -297,10 +297,15 @@ const createPlatform = (): Platform => { return { updateAvailable: true, version: next.version } }, - update: async () => { + updateAndRestart: async () => { if (!UPDATER_ENABLED || !update) return if (ostype() === "windows") await commands.killSidecar().catch(() => undefined) - await update.install().catch(() => undefined) + const installed = await update + .install() + .then(() => true) + .catch(() => false) + if (!installed) return + await relaunch() }, restart: async () => { From 6c1268f3b18ed289bc524ed10add8c3caa6131d2 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 24 Apr 2026 05:28:43 +0000 Subject: [PATCH 106/426] chore: generate --- .../app/src/components/settings-general.tsx | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 86b8c317c..f38442379 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -128,26 +128,25 @@ export const SettingsGeneral: Component = () => { return } - const actions = - platform.updateAndRestart - ? [ - { - label: language.t("toast.update.action.installRestart"), - onClick: async () => { - await platform.updateAndRestart!() - }, + const actions = platform.updateAndRestart + ? [ + { + label: language.t("toast.update.action.installRestart"), + onClick: async () => { + await platform.updateAndRestart!() }, - { - label: language.t("toast.update.action.notYet"), - onClick: "dismiss" as const, - }, - ] - : [ - { - label: language.t("toast.update.action.notYet"), - onClick: "dismiss" as const, - }, - ] + }, + { + label: language.t("toast.update.action.notYet"), + onClick: "dismiss" as const, + }, + ] + : [ + { + label: language.t("toast.update.action.notYet"), + onClick: "dismiss" as const, + }, + ] showToast({ persistent: true, From 4712f0f3c185fc3a348e2609689130fd2d901954 Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Fri, 24 Apr 2026 14:04:55 +0800 Subject: [PATCH 107/426] feat(prompt): add shell mode UI with cancel button, custom icon, and example placeholder (#24105) --- packages/app/src/components/prompt-input.tsx | 27 ++++++++++++------- .../components/prompt-input/placeholder.ts | 2 +- packages/app/src/i18n/ar.ts | 2 +- packages/app/src/i18n/br.ts | 2 +- packages/app/src/i18n/bs.ts | 2 +- packages/app/src/i18n/da.ts | 2 +- packages/app/src/i18n/de.ts | 2 +- packages/app/src/i18n/en.ts | 2 +- packages/app/src/i18n/es.ts | 2 +- packages/app/src/i18n/fr.ts | 2 +- packages/app/src/i18n/ja.ts | 2 +- packages/app/src/i18n/ko.ts | 2 +- packages/app/src/i18n/no.ts | 2 +- packages/app/src/i18n/pl.ts | 2 +- packages/app/src/i18n/ru.ts | 2 +- packages/app/src/i18n/th.ts | 2 +- packages/app/src/i18n/tr.ts | 2 +- packages/app/src/i18n/zh.ts | 2 +- packages/app/src/i18n/zht.ts | 2 +- .../.storybook/mocks/app/context/language.ts | 2 +- packages/ui/src/components/icon.tsx | 4 ++- 21 files changed, 40 insertions(+), 29 deletions(-) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 06c91c292..8b69f3b2a 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -270,7 +270,7 @@ export const PromptInput: Component = (props) => { const buttonsSpring = useSpring(() => (store.mode === "normal" ? 1 : 0), { visualDuration: 0.2, bounce: 0 }) const motion = (value: number) => ({ opacity: value, - transform: `scale(${0.95 + value * 0.05})`, + transform: `scale(${0.98 + value * 0.02})`, filter: `blur(${(1 - value) * 2}px)`, "pointer-events": value > 0.5 ? ("auto" as const) : ("none" as const), }) @@ -345,7 +345,7 @@ export const PromptInput: Component = (props) => { promptPlaceholder({ mode: store.mode, commentCount: commentCount(), - example: suggest() ? language.t(EXAMPLES[store.placeholder]) : "", + example: suggest() ? (store.mode === "shell" ? "git status" : language.t(EXAMPLES[store.placeholder])) : "", suggest: suggest(), t: (key, params) => language.t(key as Parameters[0], params as never), }), @@ -1403,12 +1403,11 @@ export const PromptInput: Component = (props) => { @@ -1451,14 +1450,24 @@ export const PromptInput: Component = (props) => {

    - {language.t("prompt.mode.shell")} -
    + + {language.t("prompt.mode.shell")} +
    +
    diff --git a/packages/app/src/components/prompt-input/placeholder.ts b/packages/app/src/components/prompt-input/placeholder.ts index 395fee51b..6669f1361 100644 --- a/packages/app/src/components/prompt-input/placeholder.ts +++ b/packages/app/src/components/prompt-input/placeholder.ts @@ -7,7 +7,7 @@ type PromptPlaceholderInput = { } export function promptPlaceholder(input: PromptPlaceholderInput) { - if (input.mode === "shell") return input.t("prompt.placeholder.shell") + if (input.mode === "shell") return input.t("prompt.placeholder.shell", { example: input.example }) if (input.commentCount > 1) return input.t("prompt.placeholder.summarizeComments") if (input.commentCount === 1) return input.t("prompt.placeholder.summarizeComment") if (!input.suggest) return input.t("prompt.placeholder.simple") diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index efb2919a5..702210e4d 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -210,7 +210,7 @@ export const dict = { "common.saving": "جارٍ الحفظ...", "common.default": "افتراضي", "common.attachment": "مرفق", - "prompt.placeholder.shell": "أدخل أمر shell...", + "prompt.placeholder.shell": "أدخل أمر shell... {{example}}", "prompt.placeholder.normal": 'اسأل أي شيء... "{{example}}"', "prompt.placeholder.simple": "اسأل أي شيء...", "prompt.placeholder.summarizeComments": "لخّص التعليقات…", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 022d01298..b414fff36 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -210,7 +210,7 @@ export const dict = { "common.saving": "Salvando...", "common.default": "Padrão", "common.attachment": "anexo", - "prompt.placeholder.shell": "Digite comando do shell...", + "prompt.placeholder.shell": "Digite comando do shell... {{example}}", "prompt.placeholder.normal": 'Pergunte qualquer coisa... "{{example}}"', "prompt.placeholder.simple": "Pergunte qualquer coisa...", "prompt.placeholder.summarizeComments": "Resumir comentários…", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index 15d8376ab..e316f87da 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -228,7 +228,7 @@ export const dict = { "common.default": "Podrazumijevano", "common.attachment": "prilog", - "prompt.placeholder.shell": "Unesi shell naredbu...", + "prompt.placeholder.shell": "Unesi shell naredbu... {{example}}", "prompt.placeholder.normal": 'Pitaj bilo šta... "{{example}}"', "prompt.placeholder.simple": "Pitaj bilo šta...", "prompt.placeholder.summarizeComments": "Sažmi komentare…", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 03cfe2b78..d368f292d 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -226,7 +226,7 @@ export const dict = { "common.default": "Standard", "common.attachment": "vedhæftning", - "prompt.placeholder.shell": "Indtast shell-kommando...", + "prompt.placeholder.shell": "Indtast shell-kommando... {{example}}", "prompt.placeholder.normal": 'Spørg om hvad som helst... "{{example}}"', "prompt.placeholder.simple": "Spørg om hvad som helst...", "prompt.placeholder.summarizeComments": "Opsummér kommentarer…", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index ccb88e9f4..a2b049c88 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -215,7 +215,7 @@ export const dict = { "common.saving": "Speichert...", "common.default": "Standard", "common.attachment": "Anhang", - "prompt.placeholder.shell": "Shell-Befehl eingeben...", + "prompt.placeholder.shell": "Shell-Befehl eingeben... {{example}}", "prompt.placeholder.normal": 'Fragen Sie alles... "{{example}}"', "prompt.placeholder.simple": "Fragen Sie alles...", "prompt.placeholder.summarizeComments": "Kommentare zusammenfassen…", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index ed80b38ce..7326f7c8b 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -230,7 +230,7 @@ export const dict = { "common.default": "Default", "common.attachment": "attachment", - "prompt.placeholder.shell": "Enter shell command...", + "prompt.placeholder.shell": "Enter shell command... {{example}}", "prompt.placeholder.normal": 'Ask anything... "{{example}}"', "prompt.placeholder.simple": "Ask anything...", "prompt.placeholder.summarizeComments": "Summarize comments…", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 0b4789c2a..8dc644bb8 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -227,7 +227,7 @@ export const dict = { "common.default": "Predeterminado", "common.attachment": "adjunto", - "prompt.placeholder.shell": "Introduce comando de shell...", + "prompt.placeholder.shell": "Introduce comando de shell... {{example}}", "prompt.placeholder.normal": 'Pregunta cualquier cosa... "{{example}}"', "prompt.placeholder.simple": "Pregunta cualquier cosa...", "prompt.placeholder.summarizeComments": "Resumir comentarios…", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 4d73f626b..1b4916c7d 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -210,7 +210,7 @@ export const dict = { "common.saving": "Enregistrement...", "common.default": "Défaut", "common.attachment": "pièce jointe", - "prompt.placeholder.shell": "Entrez une commande shell...", + "prompt.placeholder.shell": "Entrez une commande shell... {{example}}", "prompt.placeholder.normal": 'Demandez n\'importe quoi... "{{example}}"', "prompt.placeholder.simple": "Demandez n'importe quoi...", "prompt.placeholder.summarizeComments": "Résumer les commentaires…", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 493b1f17f..979f94203 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -209,7 +209,7 @@ export const dict = { "common.saving": "保存中...", "common.default": "デフォルト", "common.attachment": "添付ファイル", - "prompt.placeholder.shell": "シェルコマンドを入力...", + "prompt.placeholder.shell": "シェルコマンドを入力... {{example}}", "prompt.placeholder.normal": '何でも聞いてください... "{{example}}"', "prompt.placeholder.simple": "何でも聞いてください...", "prompt.placeholder.summarizeComments": "コメントを要約…", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 0218cc1a9..56ce374a9 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -209,7 +209,7 @@ export const dict = { "common.saving": "저장 중...", "common.default": "기본값", "common.attachment": "첨부 파일", - "prompt.placeholder.shell": "셸 명령어 입력...", + "prompt.placeholder.shell": "셸 명령어 입력... {{example}}", "prompt.placeholder.normal": '무엇이든 물어보세요... "{{example}}"', "prompt.placeholder.simple": "무엇이든 물어보세요...", "prompt.placeholder.summarizeComments": "댓글 요약…", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 43aa84420..d14dd6f98 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -230,7 +230,7 @@ export const dict = { "common.default": "Standard", "common.attachment": "vedlegg", - "prompt.placeholder.shell": "Skriv inn shell-kommando...", + "prompt.placeholder.shell": "Skriv inn shell-kommando... {{example}}", "prompt.placeholder.normal": 'Spør om hva som helst... "{{example}}"', "prompt.placeholder.simple": "Spør om hva som helst...", "prompt.placeholder.summarizeComments": "Oppsummer kommentarer…", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 6c6d4dddc..9859ea0ae 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -211,7 +211,7 @@ export const dict = { "common.saving": "Zapisywanie...", "common.default": "Domyślny", "common.attachment": "załącznik", - "prompt.placeholder.shell": "Wpisz polecenie terminala...", + "prompt.placeholder.shell": "Wpisz polecenie terminala... {{example}}", "prompt.placeholder.normal": 'Zapytaj o cokolwiek... "{{example}}"', "prompt.placeholder.simple": "Zapytaj o cokolwiek...", "prompt.placeholder.summarizeComments": "Podsumuj komentarze…", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index e0b094877..6e6ca3203 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -227,7 +227,7 @@ export const dict = { "common.default": "По умолчанию", "common.attachment": "вложение", - "prompt.placeholder.shell": "Введите команду оболочки...", + "prompt.placeholder.shell": "Введите команду оболочки... {{example}}", "prompt.placeholder.normal": 'Спросите что угодно... "{{example}}"', "prompt.placeholder.simple": "Спросите что угодно...", "prompt.placeholder.summarizeComments": "Суммировать комментарии…", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 8a15f29c0..84e5d3ff2 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -227,7 +227,7 @@ export const dict = { "common.default": "ค่าเริ่มต้น", "common.attachment": "ไฟล์แนบ", - "prompt.placeholder.shell": "ป้อนคำสั่งเชลล์...", + "prompt.placeholder.shell": "ป้อนคำสั่งเชลล์... {{example}}", "prompt.placeholder.normal": 'ถามอะไรก็ได้... "{{example}}"', "prompt.placeholder.simple": "ถามอะไรก็ได้...", "prompt.placeholder.summarizeComments": "สรุปความคิดเห็น…", diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index f20c05000..06e233cb5 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -232,7 +232,7 @@ export const dict = { "common.default": "Varsayılan", "common.attachment": "ek", - "prompt.placeholder.shell": "Kabuk komutu girin...", + "prompt.placeholder.shell": "Kabuk komutu girin... {{example}}", "prompt.placeholder.normal": 'Bir şeyler sorun... "{{example}}"', "prompt.placeholder.simple": "Bir şeyler sorun...", "prompt.placeholder.summarizeComments": "Yorumları özetle…", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 05310df96..fa83707e8 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -249,7 +249,7 @@ export const dict = { "common.default": "默认", "common.attachment": "附件", - "prompt.placeholder.shell": "输入 shell 命令...", + "prompt.placeholder.shell": "输入 shell 命令... {{example}}", "prompt.placeholder.normal": '随便问点什么... "{{example}}"', "prompt.placeholder.simple": "随便问点什么...", "prompt.placeholder.summarizeComments": "总结评论…", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 43681c779..e9d265acc 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -227,7 +227,7 @@ export const dict = { "common.default": "預設", "common.attachment": "附件", - "prompt.placeholder.shell": "輸入 shell 命令...", + "prompt.placeholder.shell": "輸入 shell 命令... {{example}}", "prompt.placeholder.normal": '隨便問點什麼... "{{example}}"', "prompt.placeholder.simple": "隨便問點什麼...", "prompt.placeholder.summarizeComments": "摘要評論…", diff --git a/packages/storybook/.storybook/mocks/app/context/language.ts b/packages/storybook/.storybook/mocks/app/context/language.ts index c3317ca2e..df28d79fb 100644 --- a/packages/storybook/.storybook/mocks/app/context/language.ts +++ b/packages/storybook/.storybook/mocks/app/context/language.ts @@ -5,7 +5,7 @@ const dict: Record = { "prompt.loading": "Loading prompt...", "prompt.placeholder.normal": "Ask anything...", "prompt.placeholder.simple": "Ask anything...", - "prompt.placeholder.shell": "Run a shell command...", + "prompt.placeholder.shell": "Run a shell command... {{example}}", "prompt.placeholder.summarizeComment": "Summarize this comment", "prompt.placeholder.summarizeComments": "Summarize these comments", "prompt.action.attachFile": "Attach files", diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx index 08726d0ff..2e4d1f53b 100644 --- a/packages/ui/src/components/icon.tsx +++ b/packages/ui/src/components/icon.tsx @@ -102,6 +102,7 @@ const icons = { link: ``, providers: ``, models: ``, + "arrow-undo-down": ``, } export interface IconProps extends ComponentProps<"svg"> { @@ -111,7 +112,8 @@ export interface IconProps extends ComponentProps<"svg"> { export function Icon(props: IconProps) { const [local, others] = splitProps(props, ["name", "size", "class", "classList"]) - const viewBox = () => (local.name === "magnifying-glass" ? "0 0 16 16" : "0 0 20 20") + const viewBox = () => + local.name === "magnifying-glass" || local.name === "arrow-undo-down" ? "0 0 16 16" : "0 0 20 20" return (
    Date: Fri, 24 Apr 2026 02:01:37 -0400 Subject: [PATCH 108/426] sync --- .../console/app/src/routes/zen/util/provider/anthropic.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/console/app/src/routes/zen/util/provider/anthropic.ts b/packages/console/app/src/routes/zen/util/provider/anthropic.ts index de49cddc1..d93bc58d8 100644 --- a/packages/console/app/src/routes/zen/util/provider/anthropic.ts +++ b/packages/console/app/src/routes/zen/util/provider/anthropic.ts @@ -53,9 +53,7 @@ export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) => anthropic_version: "bedrock-2023-05-31", anthropic_beta: supports1m ? ["context-1m-2025-08-07"] : undefined, } - : { - service_tier: "standard_only", - }), + : {}), }), createBinaryStreamDecoder: () => { if (!isBedrock) return undefined From a4bd88ab9702e1275438540812c1fb7de3ce87b5 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 24 Apr 2026 02:09:14 -0400 Subject: [PATCH 109/426] zen: deepseek v4 pro --- packages/web/src/content/docs/ar/providers.mdx | 2 +- packages/web/src/content/docs/bs/providers.mdx | 2 +- packages/web/src/content/docs/da/providers.mdx | 2 +- packages/web/src/content/docs/de/providers.mdx | 2 +- packages/web/src/content/docs/es/providers.mdx | 2 +- packages/web/src/content/docs/fr/providers.mdx | 2 +- packages/web/src/content/docs/it/providers.mdx | 2 +- packages/web/src/content/docs/ja/providers.mdx | 2 +- packages/web/src/content/docs/ko/providers.mdx | 2 +- packages/web/src/content/docs/nb/providers.mdx | 2 +- packages/web/src/content/docs/pl/providers.mdx | 2 +- packages/web/src/content/docs/providers.mdx | 2 +- packages/web/src/content/docs/pt-br/providers.mdx | 2 +- packages/web/src/content/docs/ru/providers.mdx | 2 +- packages/web/src/content/docs/th/providers.mdx | 2 +- packages/web/src/content/docs/tr/providers.mdx | 2 +- packages/web/src/content/docs/zh-cn/providers.mdx | 2 +- packages/web/src/content/docs/zh-tw/providers.mdx | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/web/src/content/docs/ar/providers.mdx b/packages/web/src/content/docs/ar/providers.mdx index 951d6701c..a43357d10 100644 --- a/packages/web/src/content/docs/ar/providers.mdx +++ b/packages/web/src/content/docs/ar/providers.mdx @@ -575,7 +575,7 @@ OpenCode Go هي خطة اشتراك منخفضة التكلفة توفّر وص └ enter ``` -4. شغّل الأمر `/models` لاختيار نموذج من DeepSeek مثل _DeepSeek Reasoner_. +4. شغّل الأمر `/models` لاختيار نموذج من DeepSeek مثل _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/bs/providers.mdx b/packages/web/src/content/docs/bs/providers.mdx index 1aae0a93a..306653f75 100644 --- a/packages/web/src/content/docs/bs/providers.mdx +++ b/packages/web/src/content/docs/bs/providers.mdx @@ -580,7 +580,7 @@ Također možete dodati modele kroz svoju opencode konfiguraciju. └ enter ``` -4. Pokrenite naredbu `/models` da odaberete DeepSeek model kao što je _DeepSeek Reasoner_. +4. Pokrenite naredbu `/models` da odaberete DeepSeek model kao što je _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/da/providers.mdx b/packages/web/src/content/docs/da/providers.mdx index bcd82c845..0bc9baa5f 100644 --- a/packages/web/src/content/docs/da/providers.mdx +++ b/packages/web/src/content/docs/da/providers.mdx @@ -571,7 +571,7 @@ Cloudflare AI Gateway lader dig få adgang til modeller fra OpenAI, Anthropic, W └ enter ``` -4. Kør kommandoen `/models` for at vælge en DeepSeek-model som _DeepSeek Reasoner_. +4. Kør kommandoen `/models` for at vælge en DeepSeek-model som _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/de/providers.mdx b/packages/web/src/content/docs/de/providers.mdx index c012a78fc..41a15fa0b 100644 --- a/packages/web/src/content/docs/de/providers.mdx +++ b/packages/web/src/content/docs/de/providers.mdx @@ -577,7 +577,7 @@ Mit dem Cloudflare AI Gateway können Sie über einen einheitlichen Endpunkt auf └ enter ``` -4. Führen Sie den Befehl `/models` aus, um ein DeepSeek-Modell wie _DeepSeek Reasoner_ auszuwählen. +4. Führen Sie den Befehl `/models` aus, um ein DeepSeek-Modell wie _DeepSeek V4 Pro_ auszuwählen. ```txt /models diff --git a/packages/web/src/content/docs/es/providers.mdx b/packages/web/src/content/docs/es/providers.mdx index b81297140..f325a0cd4 100644 --- a/packages/web/src/content/docs/es/providers.mdx +++ b/packages/web/src/content/docs/es/providers.mdx @@ -578,7 +578,7 @@ Cloudflare AI Gateway le permite acceder a modelos de OpenAI, Anthropic, Workers └ enter ``` -4. Ejecute el comando `/models` para seleccionar un modelo de DeepSeek como _DeepSeek Reasoner_. +4. Ejecute el comando `/models` para seleccionar un modelo de DeepSeek como _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/fr/providers.mdx b/packages/web/src/content/docs/fr/providers.mdx index d7b065797..2896df959 100644 --- a/packages/web/src/content/docs/fr/providers.mdx +++ b/packages/web/src/content/docs/fr/providers.mdx @@ -581,7 +581,7 @@ Vous pouvez également ajouter des modèles via votre configuration opencode. └ enter ``` -4. Exécutez la commande `/models` pour sélectionner un modèle DeepSeek tel que _DeepSeek Reasoner_. +4. Exécutez la commande `/models` pour sélectionner un modèle DeepSeek tel que _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/it/providers.mdx b/packages/web/src/content/docs/it/providers.mdx index 58bb28407..26bf19fd6 100644 --- a/packages/web/src/content/docs/it/providers.mdx +++ b/packages/web/src/content/docs/it/providers.mdx @@ -555,7 +555,7 @@ Cloudflare AI Gateway ti permette di accedere a modelli di OpenAI, Anthropic, Wo └ enter ``` -4. Esegui il comando `/models` per selezionare un modello DeepSeek come _DeepSeek Reasoner_. +4. Esegui il comando `/models` per selezionare un modello DeepSeek come _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/ja/providers.mdx b/packages/web/src/content/docs/ja/providers.mdx index f7e88d8c5..9fbb301ba 100644 --- a/packages/web/src/content/docs/ja/providers.mdx +++ b/packages/web/src/content/docs/ja/providers.mdx @@ -585,7 +585,7 @@ OpenCode 設定を通じてモデルを追加することもできます。 └ enter ``` -4. `/models` コマンドを実行して、_DeepSeek Reasoner_ のような DeepSeek モデルを選択します。 +4. `/models` コマンドを実行して、_DeepSeek V4 Pro_ のような DeepSeek モデルを選択します。 ```txt /models diff --git a/packages/web/src/content/docs/ko/providers.mdx b/packages/web/src/content/docs/ko/providers.mdx index ccbbc4838..ad4aaae7e 100644 --- a/packages/web/src/content/docs/ko/providers.mdx +++ b/packages/web/src/content/docs/ko/providers.mdx @@ -581,7 +581,7 @@ Cloudflare AI Gateway는 OpenAI, Anthropic, Workers AI 등의 모델에 액세 └ enter ``` -4. `/models` 명령을 실행하여 DeepSeek 모델(예: _DeepSeek Reasoner_)을 선택하십시오. +4. `/models` 명령을 실행하여 DeepSeek 모델(예: _DeepSeek V4 Pro_)을 선택하십시오. ```txt /models diff --git a/packages/web/src/content/docs/nb/providers.mdx b/packages/web/src/content/docs/nb/providers.mdx index 9e025a96f..ea6f1cc1a 100644 --- a/packages/web/src/content/docs/nb/providers.mdx +++ b/packages/web/src/content/docs/nb/providers.mdx @@ -579,7 +579,7 @@ Cloudflare AI Gateway lar deg få tilgang til modeller fra OpenAI, Anthropic, Wo └ enter ``` -4. Kjør kommandoen `/models` for å velge en DeepSeek-modell som _DeepSeek Reasoner_. +4. Kjør kommandoen `/models` for å velge en DeepSeek-modell som _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/pl/providers.mdx b/packages/web/src/content/docs/pl/providers.mdx index aeb627233..e2b4c70e6 100644 --- a/packages/web/src/content/docs/pl/providers.mdx +++ b/packages/web/src/content/docs/pl/providers.mdx @@ -577,7 +577,7 @@ Cloudflare AI Gateway umożliwia dostęp do modeli z OpenAI, Anthropic, Workers └ enter ``` -4. Uruchom polecenie `/models`, aby wybrać model DeepSeek, np. _DeepSeek Reasoner_. +4. Uruchom polecenie `/models`, aby wybrać model DeepSeek, np. _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index 030a50ba0..752f6c054 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -648,7 +648,7 @@ Cloudflare Workers AI lets you run AI models on Cloudflare's global network dire └ enter ``` -4. Run the `/models` command to select a DeepSeek model like _DeepSeek Reasoner_. +4. Run the `/models` command to select a DeepSeek model like _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/pt-br/providers.mdx b/packages/web/src/content/docs/pt-br/providers.mdx index 4424a55fc..58305a273 100644 --- a/packages/web/src/content/docs/pt-br/providers.mdx +++ b/packages/web/src/content/docs/pt-br/providers.mdx @@ -581,7 +581,7 @@ O Cloudflare AI Gateway permite que você acesse modelos do OpenAI, Anthropic, W └ enter ``` -4. Execute o comando `/models` para selecionar um modelo DeepSeek como _DeepSeek Reasoner_. +4. Execute o comando `/models` para selecionar um modelo DeepSeek como _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/ru/providers.mdx b/packages/web/src/content/docs/ru/providers.mdx index 1cb3873c3..144ce2ac5 100644 --- a/packages/web/src/content/docs/ru/providers.mdx +++ b/packages/web/src/content/docs/ru/providers.mdx @@ -577,7 +577,7 @@ Cloudflare AI Gateway позволяет вам получать доступ к └ enter ``` -4. Запустите команду `/models`, чтобы выбрать модель DeepSeek, например _DeepSeek Reasoner_. +4. Запустите команду `/models`, чтобы выбрать модель DeepSeek, например _DeepSeek V4 Pro_. ```txt /models diff --git a/packages/web/src/content/docs/th/providers.mdx b/packages/web/src/content/docs/th/providers.mdx index 73489b96a..c4fdb5ce3 100644 --- a/packages/web/src/content/docs/th/providers.mdx +++ b/packages/web/src/content/docs/th/providers.mdx @@ -577,7 +577,7 @@ Cloudflare AI Gateway ช่วยให้คุณเข้าถึงโม └ enter ``` -4. รันคำสั่ง `/models` เพื่อเลือกโมเดล DeepSeek เช่น _DeepSeek Reasoner_ +4. รันคำสั่ง `/models` เพื่อเลือกโมเดล DeepSeek เช่น _DeepSeek V4 Pro_ ```txt /models diff --git a/packages/web/src/content/docs/tr/providers.mdx b/packages/web/src/content/docs/tr/providers.mdx index 871f9e128..cf073e309 100644 --- a/packages/web/src/content/docs/tr/providers.mdx +++ b/packages/web/src/content/docs/tr/providers.mdx @@ -579,7 +579,7 @@ Cloudflare AI Gateway, OpenAI, Anthropic, Workers AI ve daha fazlasındaki model └ enter ``` -4. _DeepSeek Reasoner_ gibi bir DeepSeek modeli seçmek için `/models` komutunu çalıştırın. +4. _DeepSeek V4 Pro_ gibi bir DeepSeek modeli seçmek için `/models` komutunu çalıştırın. ```txt /models diff --git a/packages/web/src/content/docs/zh-cn/providers.mdx b/packages/web/src/content/docs/zh-cn/providers.mdx index 25b7d03a4..e22ba3ad8 100644 --- a/packages/web/src/content/docs/zh-cn/providers.mdx +++ b/packages/web/src/content/docs/zh-cn/providers.mdx @@ -551,7 +551,7 @@ Cloudflare AI Gateway 允许你通过统一端点访问来自 OpenAI、Anthropic └ enter ``` -4. 执行 `/models` 命令选择 DeepSeek 模型,例如 _DeepSeek Reasoner_。 +4. 执行 `/models` 命令选择 DeepSeek 模型,例如 _DeepSeek V4 Pro_。 ```txt /models diff --git a/packages/web/src/content/docs/zh-tw/providers.mdx b/packages/web/src/content/docs/zh-tw/providers.mdx index 1024444d1..8af6f01d4 100644 --- a/packages/web/src/content/docs/zh-tw/providers.mdx +++ b/packages/web/src/content/docs/zh-tw/providers.mdx @@ -572,7 +572,7 @@ Cloudflare AI Gateway 允許您透過統一端點存取來自 OpenAI、Anthropic └ enter ``` -4. 執行 `/models` 指令選擇 DeepSeek 模型,例如 _DeepSeek Reasoner_。 +4. 執行 `/models` 指令選擇 DeepSeek 模型,例如 _DeepSeek V4 Pro_。 ```txt /models From f033d2d8fbb76d654e30697c99e4b7a0beb2be8a Mon Sep 17 00:00:00 2001 From: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:20:53 +0800 Subject: [PATCH 110/426] fix(app): conditionally show model variant selector (#24115) --- packages/app/src/components/prompt-input.tsx | 54 ++++++++++---------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 8b69f3b2a..0a1809616 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -1574,33 +1574,35 @@ export const PromptInput: Component = (props) => {
    -
    - 2}> +
    - (x === "default" ? language.t("common.default") : x)} + onSelect={(value) => { + local.model.variant.set(value === "default" ? undefined : value) + restoreFocus() + }} + class="capitalize max-w-[160px] text-text-base" + valueClass="truncate text-13-regular text-text-base" + triggerStyle={control()} + triggerProps={{ "data-action": "prompt-model-variant" }} + variant="ghost" + /> + +
    +
    From 2cda629c8d28baae41cf8c861a95453b9271adf6 Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Fri, 24 Apr 2026 13:00:11 +0200 Subject: [PATCH 111/426] test(prompt): align shell placeholder expectation (#24147) --- packages/app/src/components/prompt-input/placeholder.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/prompt-input/placeholder.test.ts b/packages/app/src/components/prompt-input/placeholder.test.ts index 5f6aa59e9..d4caead0d 100644 --- a/packages/app/src/components/prompt-input/placeholder.test.ts +++ b/packages/app/src/components/prompt-input/placeholder.test.ts @@ -12,7 +12,7 @@ describe("promptPlaceholder", () => { suggest: true, t, }) - expect(value).toBe("prompt.placeholder.shell") + expect(value).toBe("prompt.placeholder.shell:example") }) test("returns summarize placeholders for comment context", () => { From a882e958b3d9bf736d9e16d65989265999674150 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:34:48 -0400 Subject: [PATCH 112/426] fix: deepseek variants (#24157) --- packages/opencode/src/provider/transform.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 1d84c7c93..6f24370ab 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -405,7 +405,10 @@ export function variants(model: Provider.Model): Record Date: Fri, 24 Apr 2026 20:42:57 +0800 Subject: [PATCH 113/426] fix: preserve empty reasoning_content for DeepSeek V4 thinking mode (#24146) Co-authored-by: Simon Klee --- packages/opencode/src/provider/transform.ts | 25 +++++++++------------ 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 6f24370ab..0a2fc7fc2 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -185,24 +185,19 @@ function normalizeMessages( // Filter out reasoning parts from content const filteredContent = msg.content.filter((part: any) => part.type !== "reasoning") - // Include reasoning_content | reasoning_details directly on the message for all assistant messages - if (reasoningText) { - return { - ...msg, - content: filteredContent, - providerOptions: { - ...msg.providerOptions, - openaiCompatible: { - ...msg.providerOptions?.openaiCompatible, - [field]: reasoningText, - }, - }, - } - } - + // Include reasoning_content | reasoning_details directly on the message for all assistant messages. + // Always set the field even when empty — some providers (e.g. DeepSeek) may return empty + // reasoning_content which still needs to be sent back in subsequent requests. return { ...msg, content: filteredContent, + providerOptions: { + ...msg.providerOptions, + openaiCompatible: { + ...msg.providerOptions?.openaiCompatible, + [field]: reasoningText, + }, + }, } } From f8e939d96fe2f2f3e347fdfd8a8f567ceb6edb01 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:48:52 -0400 Subject: [PATCH 114/426] fix: support `max` for deepseek (#24163) --- packages/opencode/src/provider/transform.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 0a2fc7fc2..7fcfcd250 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -529,7 +529,11 @@ export function variants(model: Provider.Model): Record [effort, { reasoningEffort: effort }])) + const efforts = [...WIDELY_SUPPORTED_EFFORTS] + if (model.api.id.includes("deepseek-v4")) { + efforts.push("max") + } + return Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }])) case "@ai-sdk/azure": // https://v5.ai-sdk.dev/providers/ai-sdk-providers/azure From 9f7ecd65e5534451f8feeb2ef45e1cefb4fc8484 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 24 Apr 2026 09:12:05 -0400 Subject: [PATCH 115/426] feat(httpapi): bridge file read endpoints (#24098) --- packages/opencode/specs/effect/http-api.md | 7 +- packages/opencode/src/file/index.ts | 102 +++++++++--------- .../src/server/routes/instance/file.ts | 6 +- .../server/routes/instance/httpapi/file.ts | 84 +++++++++++++++ .../server/routes/instance/httpapi/server.ts | 3 + .../src/server/routes/instance/index.ts | 4 + .../opencode/test/server/httpapi-file.test.ts | 57 ++++++++++ 7 files changed, 203 insertions(+), 60 deletions(-) create mode 100644 packages/opencode/src/server/routes/instance/httpapi/file.ts create mode 100644 packages/opencode/test/server/httpapi-file.test.ts diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 6c80dc65a..771c8b987 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -412,8 +412,9 @@ Current instance route inventory: - `workspace` - `bridged` best small reads: `GET /experimental/workspace/adaptor`, `GET /experimental/workspace`, `GET /experimental/workspace/status` defer create/remove mutations first -- `file` - `later` - good JSON-only candidate set, but larger than the current first-wave slices +- `file` - `bridged` (partial) + bridged endpoints: `GET /file`, `GET /file/content`, `GET /file/status` + defer search endpoints first - `mcp` - `later` has JSON-only endpoints, but interactive OAuth/auth flows make it a worse early fit - `session` - `defer` @@ -449,7 +450,7 @@ Recommended near-term sequence: - [x] port `project` read endpoints (`GET /project`, `GET /project/current`) - [x] port `GET /config` full read endpoint - [x] port `workspace` read endpoints -- [ ] port `file` JSON read endpoints +- [x] port `file` JSON read endpoints - [ ] decide when to remove the flag and make Effect routes the default ## Rule of thumb diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index ca791e412..05e2ce359 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -9,69 +9,63 @@ import { formatPatch, structuredPatch } from "diff" import fuzzysort from "fuzzysort" import ignore from "ignore" import path from "path" -import z from "zod" import { Global } from "../global" import { Instance } from "../project/instance" import { Log } from "../util" import { Protected } from "./protected" import { Ripgrep } from "./ripgrep" +import { zod } from "@/util/effect-zod" +import { type DeepMutable, withStatics } from "@/util/schema" -export const Info = z - .object({ - path: z.string(), - added: z.number().int(), - removed: z.number().int(), - status: z.enum(["added", "deleted", "modified"]), - }) - .meta({ - ref: "File", - }) +export const Info = Schema.Struct({ + path: Schema.String, + added: Schema.Int, + removed: Schema.Int, + status: Schema.Literals(["added", "deleted", "modified"]), +}) + .annotate({ identifier: "File" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = DeepMutable> -export type Info = z.infer +export const Node = Schema.Struct({ + name: Schema.String, + path: Schema.String, + absolute: Schema.String, + type: Schema.Literals(["file", "directory"]), + ignored: Schema.Boolean, +}) + .annotate({ identifier: "FileNode" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Node = DeepMutable> -export const Node = z - .object({ - name: z.string(), - path: z.string(), - absolute: z.string(), - type: z.enum(["file", "directory"]), - ignored: z.boolean(), - }) - .meta({ - ref: "FileNode", - }) -export type Node = z.infer +const Hunk = Schema.Struct({ + oldStart: Schema.Number, + oldLines: Schema.Number, + newStart: Schema.Number, + newLines: Schema.Number, + lines: Schema.Array(Schema.String), +}) -export const Content = z - .object({ - type: z.enum(["text", "binary"]), - content: z.string(), - diff: z.string().optional(), - patch: z - .object({ - oldFileName: z.string(), - newFileName: z.string(), - oldHeader: z.string().optional(), - newHeader: z.string().optional(), - hunks: z.array( - z.object({ - oldStart: z.number(), - oldLines: z.number(), - newStart: z.number(), - newLines: z.number(), - lines: z.array(z.string()), - }), - ), - index: z.string().optional(), - }) - .optional(), - encoding: z.literal("base64").optional(), - mimeType: z.string().optional(), - }) - .meta({ - ref: "FileContent", - }) -export type Content = z.infer +const Patch = Schema.Struct({ + oldFileName: Schema.String, + newFileName: Schema.String, + oldHeader: Schema.optional(Schema.String), + newHeader: Schema.optional(Schema.String), + hunks: Schema.Array(Hunk), + index: Schema.optional(Schema.String), +}) + +export const Content = Schema.Struct({ + type: Schema.Literals(["text", "binary"]), + content: Schema.String, + diff: Schema.optional(Schema.String), + patch: Schema.optional(Patch), + encoding: Schema.optional(Schema.Literal("base64")), + mimeType: Schema.optional(Schema.String), +}) + .annotate({ identifier: "FileContent" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Content = DeepMutable> export const Event = { Edited: BusEvent.define( diff --git a/packages/opencode/src/server/routes/instance/file.ts b/packages/opencode/src/server/routes/instance/file.ts index f92fe6e7e..661322127 100644 --- a/packages/opencode/src/server/routes/instance/file.ts +++ b/packages/opencode/src/server/routes/instance/file.ts @@ -117,7 +117,7 @@ export const FileRoutes = lazy(() => description: "Files and directories", content: { "application/json": { - schema: resolver(File.Node.array()), + schema: resolver(File.Node.zod.array()), }, }, }, @@ -146,7 +146,7 @@ export const FileRoutes = lazy(() => description: "File content", content: { "application/json": { - schema: resolver(File.Content), + schema: resolver(File.Content.zod), }, }, }, @@ -175,7 +175,7 @@ export const FileRoutes = lazy(() => description: "File status", content: { "application/json": { - schema: resolver(File.Info.array()), + schema: resolver(File.Info.zod.array()), }, }, }, diff --git a/packages/opencode/src/server/routes/instance/httpapi/file.ts b/packages/opencode/src/server/routes/instance/httpapi/file.ts new file mode 100644 index 000000000..eaf43862a --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/file.ts @@ -0,0 +1,84 @@ +import { File } from "@/file" +import { Effect, Layer, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" + +const FileQuery = Schema.Struct({ + path: Schema.String, +}) + +export const FilePaths = { + list: "/file", + content: "/file/content", + status: "/file/status", +} as const + +export const FileApi = HttpApi.make("file") + .add( + HttpApiGroup.make("file") + .add( + HttpApiEndpoint.get("list", FilePaths.list, { + query: FileQuery, + success: Schema.Array(File.Node), + }).annotateMerge( + OpenApi.annotations({ + identifier: "file.list", + summary: "List files", + description: "List files and directories in a specified path.", + }), + ), + HttpApiEndpoint.get("content", FilePaths.content, { + query: FileQuery, + success: File.Content, + }).annotateMerge( + OpenApi.annotations({ + identifier: "file.read", + summary: "Read file", + description: "Read the content of a specified file.", + }), + ), + HttpApiEndpoint.get("status", FilePaths.status, { + success: Schema.Array(File.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "file.status", + summary: "Get file status", + description: "Get the git status of all files in the project.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "file", + description: "Experimental HttpApi file routes.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "opencode experimental HttpApi", + version: "0.0.1", + description: "Experimental HttpApi surface for selected instance routes.", + }), + ) + +export const fileHandlers = Layer.unwrap( + Effect.gen(function* () { + const svc = yield* File.Service + + const list = Effect.fn("FileHttpApi.list")(function* (ctx: { query: { path: string } }) { + return yield* svc.list(ctx.query.path) + }) + + const content = Effect.fn("FileHttpApi.content")(function* (ctx: { query: { path: string } }) { + return yield* svc.read(ctx.query.path) + }) + + const status = Effect.fn("FileHttpApi.status")(function* () { + return yield* svc.status() + }) + + return HttpApiBuilder.group(FileApi, "file", (handlers) => + handlers.handle("list", list).handle("content", content).handle("status", status), + ) + }), +).pipe(Layer.provide(File.defaultLayer)) diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index 7b131d400..c57320c10 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -10,6 +10,7 @@ import { Instance } from "@/project/instance" import { lazy } from "@/util/lazy" import { Filesystem } from "@/util" import { ConfigApi, configHandlers } from "./config" +import { FileApi, fileHandlers } from "./file" import { PermissionApi, permissionHandlers } from "./permission" import { ProjectApi, projectHandlers } from "./project" import { ProviderApi, providerHandlers } from "./provider" @@ -114,9 +115,11 @@ const ProjectSecured = ProjectApi.middleware(Authorization) const ProviderSecured = ProviderApi.middleware(Authorization) const ConfigSecured = ConfigApi.middleware(Authorization) const WorkspaceSecured = WorkspaceApi.middleware(Authorization) +const FileSecured = FileApi.middleware(Authorization) export const routes = Layer.mergeAll( HttpApiBuilder.layer(ConfigSecured).pipe(Layer.provide(configHandlers)), + HttpApiBuilder.layer(FileSecured).pipe(Layer.provide(fileHandlers)), HttpApiBuilder.layer(ProjectSecured).pipe(Layer.provide(projectHandlers)), HttpApiBuilder.layer(QuestionSecured).pipe(Layer.provide(questionHandlers)), HttpApiBuilder.layer(PermissionSecured).pipe(Layer.provide(permissionHandlers)), diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index e8a038fab..fba150116 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -16,6 +16,7 @@ import { QuestionRoutes } from "./question" import { PermissionRoutes } from "./permission" import { Flag } from "@/flag/flag" import { ExperimentalHttpApiServer } from "./httpapi/server" +import { FilePaths } from "./httpapi/file" import { ProjectRoutes } from "./project" import { SessionRoutes } from "./session" import { PtyRoutes } from "./pty" @@ -48,6 +49,9 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.post("/provider/:providerID/oauth/callback", (c) => handler(c.req.raw, context)) app.get("/project", (c) => handler(c.req.raw, context)) app.get("/project/current", (c) => handler(c.req.raw, context)) + app.get(FilePaths.list, (c) => handler(c.req.raw, context)) + app.get(FilePaths.content, (c) => handler(c.req.raw, context)) + app.get(FilePaths.status, (c) => handler(c.req.raw, context)) } return app diff --git a/packages/opencode/test/server/httpapi-file.test.ts b/packages/opencode/test/server/httpapi-file.test.ts new file mode 100644 index 000000000..5a9058cb6 --- /dev/null +++ b/packages/opencode/test/server/httpapi-file.test.ts @@ -0,0 +1,57 @@ +import { afterEach, describe, expect, test } from "bun:test" +import { Context } from "effect" +import path from "path" +import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" +import { FilePaths } from "../../src/server/routes/instance/httpapi/file" +import { Instance } from "../../src/project/instance" +import { Log } from "../../src/util" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" + +void Log.init({ print: false }) + +const context = Context.empty() as Context.Context + +function request(route: string, directory: string, query?: Record) { + const url = new URL(`http://localhost${route}`) + for (const [key, value] of Object.entries(query ?? {})) { + url.searchParams.set(key, value) + } + return ExperimentalHttpApiServer.webHandler().handler( + new Request(url, { + headers: { + "x-opencode-directory": directory, + }, + }), + context, + ) +} + +afterEach(async () => { + await Instance.disposeAll() + await resetDatabase() +}) + +describe("file HttpApi", () => { + test("serves read endpoints", async () => { + await using tmp = await tmpdir({ git: true }) + await Bun.write(path.join(tmp.path, "hello.txt"), "hello") + + const [list, content, status] = await Promise.all([ + request(FilePaths.list, tmp.path, { path: "." }), + request(FilePaths.content, tmp.path, { path: "hello.txt" }), + request(FilePaths.status, tmp.path), + ]) + + expect(list.status).toBe(200) + expect(await list.json()).toContainEqual( + expect.objectContaining({ name: "hello.txt", path: "hello.txt", type: "file" }), + ) + + expect(content.status).toBe(200) + expect(await content.json()).toMatchObject({ type: "text", content: "hello" }) + + expect(status.status).toBe(200) + expect(await status.json()).toContainEqual({ path: "hello.txt", added: 1, removed: 0, status: "added" }) + }) +}) From a8c8d2dd79cdb98b673a92da738e584e9964b4eb Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 24 Apr 2026 13:17:13 +0000 Subject: [PATCH 116/426] sync release versions for v1.14.23 --- bun.lock | 32 +++++++++++++------------- 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-electron/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/shared/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index 06a5cfdb4..16ada2be0 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -83,7 +83,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -117,7 +117,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -144,7 +144,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -168,7 +168,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -192,7 +192,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -225,7 +225,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -269,7 +269,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -298,7 +298,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -314,7 +314,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.14.22", + "version": "1.14.23", "bin": { "opencode": "./bin/opencode", }, @@ -459,7 +459,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -494,7 +494,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "cross-spawn": "catalog:", }, @@ -509,7 +509,7 @@ }, "packages/shared": { "name": "@opencode-ai/shared", - "version": "1.14.22", + "version": "1.14.23", "bin": { "opencode": "./bin/opencode", }, @@ -533,7 +533,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -568,7 +568,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -617,7 +617,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.14.22", + "version": "1.14.23", "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 0ab5e304a..b01b9b9d1 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.14.22", + "version": "1.14.23", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 3d3752750..acd7024a6 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 70d44b382..a8ba8c539 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.14.22", + "version": "1.14.23", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 55791a307..b917ee2b5 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.14.22", + "version": "1.14.23", "$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 4966bf3e6..17fd13d0f 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.14.22", + "version": "1.14.23", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index a528a9a3a..bd286786b 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 32b63fff1..561a97470 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index e4428f0cb..11a6ea7ef 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.14.22", + "version": "1.14.23", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 6449b8df6..2943614d3 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.14.22" +version = "1.14.23" 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.14.22/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/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.14.22/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/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.14.22/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 5b06d0368..1501f3df4 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.14.22", + "version": "1.14.23", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index ea8724ceb..5225ff54c 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.22", + "version": "1.14.23", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index ee8210ec1..16c5e236b 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.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 62a50b743..9f9658eb1 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.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/shared/package.json b/packages/shared/package.json index 48ae0fb78..08bb00929 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.22", + "version": "1.14.23", "name": "@opencode-ai/shared", "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index a3516932a..5149dbffe 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index ba0fb0149..f4d90f146 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.14.22", + "version": "1.14.23", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 818cdf721..386078d17 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.14.22", + "version": "1.14.23", "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 e238b3670..035a45eb7 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.14.22", + "version": "1.14.23", "publisher": "sst-dev", "repository": { "type": "git", From 011c23761bd7ebe9df0b772b409a353051d9489c Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 24 Apr 2026 09:18:57 -0400 Subject: [PATCH 117/426] feat(httpapi): bridge mcp status endpoint (#24100) --- packages/opencode/specs/effect/http-api.md | 6 +- packages/opencode/src/mcp/index.ts | 73 ++++++++----------- .../src/server/routes/instance/httpapi/mcp.ts | 48 ++++++++++++ .../server/routes/instance/httpapi/server.ts | 3 + .../src/server/routes/instance/index.ts | 2 + .../src/server/routes/instance/mcp.ts | 8 +- .../opencode/test/server/httpapi-mcp.test.ts | 48 ++++++++++++ 7 files changed, 138 insertions(+), 50 deletions(-) create mode 100644 packages/opencode/src/server/routes/instance/httpapi/mcp.ts create mode 100644 packages/opencode/test/server/httpapi-mcp.test.ts diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 771c8b987..f83b7c055 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -415,8 +415,9 @@ Current instance route inventory: - `file` - `bridged` (partial) bridged endpoints: `GET /file`, `GET /file/content`, `GET /file/status` defer search endpoints first -- `mcp` - `later` - has JSON-only endpoints, but interactive OAuth/auth flows make it a worse early fit +- `mcp` - `bridged` (partial) + bridged endpoints: `GET /mcp` + defer interactive OAuth/auth flows first - `session` - `defer` large, stateful, mixes CRUD with prompt/shell/command/share/revert flows and a streaming route - `event` - `defer` @@ -451,6 +452,7 @@ Recommended near-term sequence: - [x] port `GET /config` full read endpoint - [x] port `workspace` read endpoints - [x] port `file` JSON read endpoints +- [x] port `mcp` status read endpoint - [ ] decide when to remove the flag and make Effect routes the default ## Rule of thumb diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 385d7782a..3c6816c5b 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -30,6 +30,8 @@ import { EffectBridge } from "@/effect" import { InstanceState } from "@/effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { zod as effectZod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" const log = Log.create({ service: "mcp" }) const DEFAULT_TIMEOUT = 30_000 @@ -69,50 +71,33 @@ export const Failed = NamedError.create( type MCPClient = Client -export const Status = z - .discriminatedUnion("status", [ - z - .object({ - status: z.literal("connected"), - }) - .meta({ - ref: "MCPStatusConnected", - }), - z - .object({ - status: z.literal("disabled"), - }) - .meta({ - ref: "MCPStatusDisabled", - }), - z - .object({ - status: z.literal("failed"), - error: z.string(), - }) - .meta({ - ref: "MCPStatusFailed", - }), - z - .object({ - status: z.literal("needs_auth"), - }) - .meta({ - ref: "MCPStatusNeedsAuth", - }), - z - .object({ - status: z.literal("needs_client_registration"), - error: z.string(), - }) - .meta({ - ref: "MCPStatusNeedsClientRegistration", - }), - ]) - .meta({ - ref: "MCPStatus", - }) -export type Status = z.infer +const StatusConnected = Schema.Struct({ status: Schema.Literal("connected") }).annotate({ + identifier: "MCPStatusConnected", +}) +const StatusDisabled = Schema.Struct({ status: Schema.Literal("disabled") }).annotate({ + identifier: "MCPStatusDisabled", +}) +const StatusFailed = Schema.Struct({ status: Schema.Literal("failed"), error: Schema.String }).annotate({ + identifier: "MCPStatusFailed", +}) +const StatusNeedsAuth = Schema.Struct({ status: Schema.Literal("needs_auth") }).annotate({ + identifier: "MCPStatusNeedsAuth", +}) +const StatusNeedsClientRegistration = Schema.Struct({ + status: Schema.Literal("needs_client_registration"), + error: Schema.String, +}).annotate({ identifier: "MCPStatusNeedsClientRegistration" }) + +export const Status = Schema.Union([ + StatusConnected, + StatusDisabled, + StatusFailed, + StatusNeedsAuth, + StatusNeedsClientRegistration, +]) + .annotate({ identifier: "MCPStatus", discriminator: "status" }) + .pipe(withStatics((s) => ({ zod: effectZod(s) }))) +export type Status = Schema.Schema.Type // Store transports for OAuth servers to allow finishing auth type TransportWithAuth = StreamableHTTPClientTransport | SSEClientTransport diff --git a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts new file mode 100644 index 000000000..91467b1e9 --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts @@ -0,0 +1,48 @@ +import { MCP } from "@/mcp" +import { Effect, Layer, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" + +export const McpPaths = { + status: "/mcp", +} as const + +export const McpApi = HttpApi.make("mcp") + .add( + HttpApiGroup.make("mcp") + .add( + HttpApiEndpoint.get("status", McpPaths.status, { + success: Schema.Record(Schema.String, MCP.Status), + }).annotateMerge( + OpenApi.annotations({ + identifier: "mcp.status", + summary: "Get MCP status", + description: "Get the status of all Model Context Protocol (MCP) servers.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "mcp", + description: "Experimental HttpApi MCP routes.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "opencode experimental HttpApi", + version: "0.0.1", + description: "Experimental HttpApi surface for selected instance routes.", + }), + ) + +export const mcpHandlers = Layer.unwrap( + Effect.gen(function* () { + const mcp = yield* MCP.Service + + const status = Effect.fn("McpHttpApi.status")(function* () { + return yield* mcp.status() + }) + + return HttpApiBuilder.group(McpApi, "mcp", (handlers) => handlers.handle("status", status)) + }), +).pipe(Layer.provide(MCP.defaultLayer)) diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index c57320c10..b45ad942b 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -11,6 +11,7 @@ import { lazy } from "@/util/lazy" import { Filesystem } from "@/util" import { ConfigApi, configHandlers } from "./config" import { FileApi, fileHandlers } from "./file" +import { McpApi, mcpHandlers } from "./mcp" import { PermissionApi, permissionHandlers } from "./permission" import { ProjectApi, projectHandlers } from "./project" import { ProviderApi, providerHandlers } from "./provider" @@ -116,10 +117,12 @@ const ProviderSecured = ProviderApi.middleware(Authorization) const ConfigSecured = ConfigApi.middleware(Authorization) const WorkspaceSecured = WorkspaceApi.middleware(Authorization) const FileSecured = FileApi.middleware(Authorization) +const McpSecured = McpApi.middleware(Authorization) export const routes = Layer.mergeAll( HttpApiBuilder.layer(ConfigSecured).pipe(Layer.provide(configHandlers)), HttpApiBuilder.layer(FileSecured).pipe(Layer.provide(fileHandlers)), + HttpApiBuilder.layer(McpSecured).pipe(Layer.provide(mcpHandlers)), HttpApiBuilder.layer(ProjectSecured).pipe(Layer.provide(projectHandlers)), HttpApiBuilder.layer(QuestionSecured).pipe(Layer.provide(questionHandlers)), HttpApiBuilder.layer(PermissionSecured).pipe(Layer.provide(permissionHandlers)), diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index fba150116..b899eb108 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -17,6 +17,7 @@ import { PermissionRoutes } from "./permission" import { Flag } from "@/flag/flag" import { ExperimentalHttpApiServer } from "./httpapi/server" import { FilePaths } from "./httpapi/file" +import { McpPaths } from "./httpapi/mcp" import { ProjectRoutes } from "./project" import { SessionRoutes } from "./session" import { PtyRoutes } from "./pty" @@ -52,6 +53,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get(FilePaths.list, (c) => handler(c.req.raw, context)) app.get(FilePaths.content, (c) => handler(c.req.raw, context)) app.get(FilePaths.status, (c) => handler(c.req.raw, context)) + app.get(McpPaths.status, (c) => handler(c.req.raw, context)) } return app diff --git a/packages/opencode/src/server/routes/instance/mcp.ts b/packages/opencode/src/server/routes/instance/mcp.ts index ce4722933..2de8a7125 100644 --- a/packages/opencode/src/server/routes/instance/mcp.ts +++ b/packages/opencode/src/server/routes/instance/mcp.ts @@ -21,7 +21,7 @@ export const McpRoutes = lazy(() => description: "MCP server status", content: { "application/json": { - schema: resolver(z.record(z.string(), MCP.Status)), + schema: resolver(z.record(z.string(), MCP.Status.zod)), }, }, }, @@ -44,7 +44,7 @@ export const McpRoutes = lazy(() => description: "MCP server added successfully", content: { "application/json": { - schema: resolver(z.record(z.string(), MCP.Status)), + schema: resolver(z.record(z.string(), MCP.Status.zod)), }, }, }, @@ -121,7 +121,7 @@ export const McpRoutes = lazy(() => description: "OAuth authentication completed", content: { "application/json": { - schema: resolver(MCP.Status), + schema: resolver(MCP.Status.zod), }, }, }, @@ -153,7 +153,7 @@ export const McpRoutes = lazy(() => description: "OAuth authentication completed", content: { "application/json": { - schema: resolver(MCP.Status), + schema: resolver(MCP.Status.zod), }, }, }, diff --git a/packages/opencode/test/server/httpapi-mcp.test.ts b/packages/opencode/test/server/httpapi-mcp.test.ts new file mode 100644 index 000000000..3da1dc933 --- /dev/null +++ b/packages/opencode/test/server/httpapi-mcp.test.ts @@ -0,0 +1,48 @@ +import { afterEach, describe, expect, test } from "bun:test" +import { Context } from "effect" +import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" +import { McpPaths } from "../../src/server/routes/instance/httpapi/mcp" +import { Instance } from "../../src/project/instance" +import { Log } from "../../src/util" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" + +void Log.init({ print: false }) + +const context = Context.empty() as Context.Context + +function request(route: string, directory: string) { + return ExperimentalHttpApiServer.webHandler().handler( + new Request(`http://localhost${route}`, { + headers: { + "x-opencode-directory": directory, + }, + }), + context, + ) +} + +afterEach(async () => { + await Instance.disposeAll() + await resetDatabase() +}) + +describe("mcp HttpApi", () => { + test("serves status endpoint", async () => { + await using tmp = await tmpdir({ + config: { + mcp: { + demo: { + type: "local", + command: ["echo", "demo"], + enabled: false, + }, + }, + }, + }) + + const response = await request(McpPaths.status, tmp.path) + expect(response.status).toBe(200) + expect(await response.json()).toEqual({ demo: { status: "disabled" } }) + }) +}) From d89bfc32ac0c7d2546f3d6c8096171b32279623b Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 24 Apr 2026 13:20:23 +0000 Subject: [PATCH 118/426] chore: generate --- packages/opencode/src/server/routes/instance/mcp.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/mcp.ts b/packages/opencode/src/server/routes/instance/mcp.ts index 2de8a7125..b47a6d29a 100644 --- a/packages/opencode/src/server/routes/instance/mcp.ts +++ b/packages/opencode/src/server/routes/instance/mcp.ts @@ -121,7 +121,7 @@ export const McpRoutes = lazy(() => description: "OAuth authentication completed", content: { "application/json": { - schema: resolver(MCP.Status.zod), + schema: resolver(MCP.Status.zod), }, }, }, @@ -153,7 +153,7 @@ export const McpRoutes = lazy(() => description: "OAuth authentication completed", content: { "application/json": { - schema: resolver(MCP.Status.zod), + schema: resolver(MCP.Status.zod), }, }, }, From 3062d3e0705a3959ff1108c8aa699358c8ea05a7 Mon Sep 17 00:00:00 2001 From: 07akioni <07akioni2@gmail.com> Date: Fri, 24 Apr 2026 22:41:41 +0800 Subject: [PATCH 119/426] fix: use existingModel as fallback for interleaved field (#24172) --- packages/opencode/src/provider/provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index d826f6b35..0fe53e6e4 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1177,7 +1177,7 @@ const layer: Layer.Layer< model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false, pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false, }, - interleaved: model.interleaved ?? false, + interleaved: model.interleaved ?? existingModel?.capabilities.interleaved ?? false, }, cost: { input: model?.cost?.input ?? existingModel?.cost?.input ?? 0, From 86715fecc469e7bf12e526d386e4927afc95fa3e Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Fri, 24 Apr 2026 11:10:47 -0400 Subject: [PATCH 120/426] fix: ensure assistant messages always have reasoning on them for deepseek (#24180) --- packages/opencode/src/provider/transform.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 7fcfcd250..50529c4dd 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -175,6 +175,24 @@ function normalizeMessages( return result } + // Deepseek requires all assistant messages to have reasoning on them + if (model.api.id.includes("deepseek")) { + msgs = msgs.map((msg) => { + if (msg.role !== "assistant") return msg + if (Array.isArray(msg.content)) { + if (msg.content.some((part) => part.type === "reasoning")) return msg + return { ...msg, content: [...msg.content, { type: "reasoning", text: "" }] } + } + return { + ...msg, + content: [ + ...(msg.content ? [{ type: "text" as const, text: msg.content }] : []), + { type: "reasoning" as const, text: "" }, + ], + } + }) + } + if (typeof model.capabilities.interleaved === "object" && model.capabilities.interleaved.field) { const field = model.capabilities.interleaved.field return msgs.map((msg) => { From 3a5507de95b36da811fc5d3521769a946b883174 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 24 Apr 2026 17:28:07 +0200 Subject: [PATCH 121/426] Use OpenTUI theme detection for initial TUI mode, again (#23846) --- bun.lock | 28 ++++++------- packages/opencode/package.json | 4 +- packages/opencode/src/cli/cmd/tui/app.tsx | 8 +--- .../src/cli/cmd/tui/context/theme.tsx | 2 +- .../opencode/src/cli/cmd/tui/util/terminal.ts | 39 ------------------- packages/plugin/package.json | 8 ++-- 6 files changed, 22 insertions(+), 67 deletions(-) diff --git a/bun.lock b/bun.lock index 16ada2be0..359709a46 100644 --- a/bun.lock +++ b/bun.lock @@ -367,8 +367,8 @@ "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", - "@opentui/core": "0.1.99", - "@opentui/solid": "0.1.99", + "@opentui/core": "0.1.103", + "@opentui/solid": "0.1.103", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -466,16 +466,16 @@ "zod": "catalog:", }, "devDependencies": { - "@opentui/core": "0.1.99", - "@opentui/solid": "0.1.99", + "@opentui/core": "0.1.103", + "@opentui/solid": "0.1.103", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "@typescript/native-preview": "catalog:", "typescript": "catalog:", }, "peerDependencies": { - "@opentui/core": ">=0.1.99", - "@opentui/solid": ">=0.1.99", + "@opentui/core": ">=0.1.103", + "@opentui/solid": ">=0.1.103", }, "optionalPeers": [ "@opentui/core", @@ -1604,21 +1604,21 @@ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], - "@opentui/core": ["@opentui/core@0.1.99", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.99", "@opentui/core-darwin-x64": "0.1.99", "@opentui/core-linux-arm64": "0.1.99", "@opentui/core-linux-x64": "0.1.99", "@opentui/core-win32-arm64": "0.1.99", "@opentui/core-win32-x64": "0.1.99", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-I3+AEgGzqNWIpWX9g2WOscSPwtQDNOm4KlBjxBWCZjLxkF07u77heWXF7OiAdhKLtNUW6TFiyt6yznqAZPdG3A=="], + "@opentui/core": ["@opentui/core@0.1.103", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.103", "@opentui/core-darwin-x64": "0.1.103", "@opentui/core-linux-arm64": "0.1.103", "@opentui/core-linux-x64": "0.1.103", "@opentui/core-win32-arm64": "0.1.103", "@opentui/core-win32-x64": "0.1.103", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-PWVv/bDmlk1i6X1f0zXs+jSaTrQ/ByX8wFbP2WinOObTGf//UbcRP4dbWxPXvOyka9QlmRBG/7GbloQSIStyVw=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.99", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bzVrqeX2vb5iWrc/ftOUOqeUY8XO+qSgoTwj5TXHuwagavgwD3Hpeyjx8+icnTTeM4pao0som1WR9xfye6/X5Q=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.103", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lxCyedDkcen12IgBtXjkJ7iY66xa7VC4nxRNKCUeLY2ZP9hUE1AsDtbyQzqY+BQadsI/ZME9STzaHDCUFg0TpA=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.99", "", { "os": "darwin", "cpu": "x64" }, "sha512-VE4FrXBYpkxnvkqcCV1a8aN9jyyMJMihVW+V2NLCtp+4yQsj0AapG5TiUSN76XnmSZRptxDy5rBmEempeoIZbg=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.103", "", { "os": "darwin", "cpu": "x64" }, "sha512-QMYD+zUDGQliJ6m5nuNvA72jtluFeyVMoHkuA5m/Xmed/u8eLfahAKmDj3kY66ntUroPHWevcpbpvd7NCFEoFQ=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.99", "", { "os": "linux", "cpu": "arm64" }, "sha512-viXQsbpS7yHjYkl7+am32JdvG96QU9lvHh1UiZtpOxcNUUqiYmA2ZwZFPD2Bi54jNyj5l2hjH6YkD3DzE2FEWA=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.103", "", { "os": "linux", "cpu": "arm64" }, "sha512-GzOvNr9dN6JaQ9qs7m8E75wLAHwT5CyxqkE6rEr1BO23/d2Ix7e3GYw/JRY5VnTge+eXrfDVbqNtPcQamUNiEA=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.99", "", { "os": "linux", "cpu": "x64" }, "sha512-WLoEFINOSp0tZSR9y4LUuGc7n4Y7H1wcpjUPzQ9vChkYDXrfZltEanzoDWbDcQ4kZQW5tHVC7LrZHpAsRLwFZg=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.103", "", { "os": "linux", "cpu": "x64" }, "sha512-odywllco5zUKNc60uD3JKaCybK64u6BfmpScs4a8Qn89yH/yk23bzWXDRWaGgQdY65L2/VCbcGs1ezA1S/2YTw=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.99", "", { "os": "win32", "cpu": "arm64" }, "sha512-yWMOLWCEO8HdrctU1dMkgZC8qGkiO4Dwr4/e11tTvVpRmYhDsP/IR89ZjEEtOwnKwFOFuB/MxvflqaEWVQ2g5Q=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.103", "", { "os": "win32", "cpu": "arm64" }, "sha512-tZL5w3Y0JnO7RkIvfuNDAzJn0j6+JIYl6M8DgPM5p8AQt+162S8LmbumzmqQLZl4cEev2eN7/tw72WIk6b+/CQ=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.99", "", { "os": "win32", "cpu": "x64" }, "sha512-aYRlsL2w8YRL6vPd7/hrqlNVkXU3QowWb01TOvAcHS8UAsXaGFUr47kSDyjxDi1wg1MzmVduCfsC7T3NoThV1w=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.103", "", { "os": "win32", "cpu": "x64" }, "sha512-wqnibt/OE5ldSzVPxEbriA0TjI2B11CJl4uJoLxTZ47KDx7tAFIMdhBf9IRAGNCSbcDuZ8ZGEFhV+SLaftkrlw=="], - "@opentui/solid": ["@opentui/solid@0.1.99", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.99", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-DrqqO4h2V88FmeIP2cErYkMU0ZK5MrUsZw3w6IzZpoXyyiL4/9qpWzUq+CXx+r16VP2iGxDJwGKUmtFAzUch2Q=="], + "@opentui/solid": ["@opentui/solid@0.1.103", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.103", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-L28WFBs17Z5JXkJhPagLy8tUamkttDaaQDdb2KEO01IQ9r81yLBRpYqD/lHp6UoSISXgwQVDQ9yNtxwR1BQZvQ=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 5225ff54c..f1d72c640 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -123,8 +123,8 @@ "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", - "@opentui/core": "0.1.99", - "@opentui/solid": "0.1.99", + "@opentui/core": "0.1.103", + "@opentui/solid": "0.1.103", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index eb5cb44e8..30a597b91 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -1,7 +1,6 @@ import { render, TimeToFirstDraw, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid" import * as Clipboard from "@tui/util/clipboard" import * as Selection from "@tui/util/selection" -import * as Terminal from "@tui/util/terminal" import { createCliRenderer, MouseButton, type CliRendererConfig } from "@opentui/core" import { RouteProvider, useRoute } from "@tui/context/route" import { @@ -121,12 +120,6 @@ export function tui(input: { const unguard = win32InstallCtrlCGuard() win32DisableProcessedInput() - const mode = await Terminal.getTerminalBackgroundColor() - - // Re-clear after getTerminalBackgroundColor() because setRawMode(false) - // restores the original console mode, including processed input on Windows. - win32DisableProcessedInput() - const onExit = async () => { unguard?.() resolve() @@ -137,6 +130,7 @@ export function tui(input: { } const renderer = await createCliRenderer(rendererConfig(input.config)) + const mode = (await renderer.waitForThemeMode(1000)) ?? "dark" await render(() => { return ( diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx index af9582cfb..3ae1eb869 100644 --- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx @@ -314,7 +314,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ setStore( produce((draft) => { const lock = pick(kv.get("theme_mode_lock")) - const mode = lock ?? props.mode + const mode = lock ?? pick(renderer.themeMode) ?? props.mode if (!lock && pick(kv.get("theme_mode")) !== undefined) { kv.set("theme_mode", undefined) } diff --git a/packages/opencode/src/cli/cmd/tui/util/terminal.ts b/packages/opencode/src/cli/cmd/tui/util/terminal.ts index a61390f2c..c026b7381 100644 --- a/packages/opencode/src/cli/cmd/tui/util/terminal.ts +++ b/packages/opencode/src/cli/cmd/tui/util/terminal.ts @@ -17,12 +17,6 @@ function parse(color: string): RGBA | null { return null } -function mode(background: RGBA | null): "dark" | "light" { - if (!background) return "dark" - const luminance = (0.299 * background.r + 0.587 * background.g + 0.114 * background.b) / 255 - return luminance > 0.5 ? "light" : "dark" -} - /** * Query terminal colors including background, foreground, and palette (0-15). * Uses OSC escape sequences to retrieve actual terminal color values. @@ -100,36 +94,3 @@ export async function colors(): Promise<{ }, 1000) }) } - -// Keep startup mode detection separate from `colors()`: the TUI boot path only -// needs OSC 11 and should resolve on the first background response instead of -// waiting on the full palette query used by system theme generation. -export async function getTerminalBackgroundColor(): Promise<"dark" | "light"> { - if (!process.stdin.isTTY) return "dark" - - return new Promise((resolve) => { - let timeout: NodeJS.Timeout - - const cleanup = () => { - process.stdin.setRawMode(false) - process.stdin.removeListener("data", handler) - clearTimeout(timeout) - } - - const handler = (data: Buffer) => { - const match = data.toString().match(/\x1b]11;([^\x07\x1b]+)/) - if (!match) return - cleanup() - resolve(mode(parse(match[1]))) - } - - process.stdin.setRawMode(true) - process.stdin.on("data", handler) - process.stdout.write("\x1b]11;?\x07") - - timeout = setTimeout(() => { - cleanup() - resolve("dark") - }, 1000) - }) -} diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 16c5e236b..45926f05b 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -22,8 +22,8 @@ "zod": "catalog:" }, "peerDependencies": { - "@opentui/core": ">=0.1.99", - "@opentui/solid": ">=0.1.99" + "@opentui/core": ">=0.1.103", + "@opentui/solid": ">=0.1.103" }, "peerDependenciesMeta": { "@opentui/core": { @@ -34,8 +34,8 @@ } }, "devDependencies": { - "@opentui/core": "0.1.99", - "@opentui/solid": "0.1.99", + "@opentui/core": "0.1.103", + "@opentui/solid": "0.1.103", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "typescript": "catalog:", From 66936b0fffef0c996b4c6e3aa902662c8c6005f4 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 24 Apr 2026 15:45:10 +0000 Subject: [PATCH 122/426] 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 c09604610..07d53e889 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-AgHhYsiygxbsBo3JN4HqHXKAwh8n1qeuSCe2qqxlxW4=", - "aarch64-linux": "sha256-h2lpWRQ5EDYnjpqZXtUAp1mxKLQxJ4m8MspgSY8Ev78=", - "aarch64-darwin": "sha256-xnd91+WyeAqn06run2ajsekxJvTMiLsnqNPe/rR8VTM=", - "x86_64-darwin": "sha256-rXpz45IOjGEk73xhP9VY86eOj2CZBg2l1vzwzTIOOOQ=" + "x86_64-linux": "sha256-+G3/s18NZO1Dpc5TsZyix2Npodzei25Svw3nTjfzXW8=", + "aarch64-linux": "sha256-39HPencmRYRbyCk/cZIdPFk6ocY1AMlyuN9j25zAKzI=", + "aarch64-darwin": "sha256-043korPEjSHKiZ3P+EfWyOfKpgOC7CBpviccviaDa0o=", + "x86_64-darwin": "sha256-vsZ7e//rL9e7Cl5kl/Xplvi1fqayljxTLwRSbxvCxeM=" } } From 28f7d31e72f2556b09442dbddcd0816c1a88b1f1 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 24 Apr 2026 11:45:56 -0400 Subject: [PATCH 123/426] zen: deepseek v4 pro --- packages/console/app/src/i18n/ar.ts | 8 +-- packages/console/app/src/i18n/br.ts | 8 +-- packages/console/app/src/i18n/da.ts | 8 +-- packages/console/app/src/i18n/de.ts | 8 +-- packages/console/app/src/i18n/en.ts | 8 +-- packages/console/app/src/i18n/es.ts | 8 +-- packages/console/app/src/i18n/fr.ts | 8 +-- packages/console/app/src/i18n/it.ts | 8 +-- packages/console/app/src/i18n/ja.ts | 8 +-- packages/console/app/src/i18n/ko.ts | 8 +-- packages/console/app/src/i18n/no.ts | 8 +-- packages/console/app/src/i18n/pl.ts | 8 +-- packages/console/app/src/i18n/ru.ts | 8 +-- packages/console/app/src/i18n/th.ts | 8 +-- packages/console/app/src/i18n/tr.ts | 8 +-- packages/console/app/src/i18n/zh.ts | 8 +-- packages/console/app/src/i18n/zht.ts | 8 +-- packages/console/app/src/routes/go/index.tsx | 12 ++-- .../routes/workspace/[id]/go/lite-section.tsx | 2 + packages/web/src/content/docs/ar/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/bs/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/da/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/de/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/es/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/fr/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/it/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/ja/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/ko/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/nb/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/pl/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/pt-br/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/ru/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/th/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/tr/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/zh-cn/go.mdx | 63 ++++++++++--------- packages/web/src/content/docs/zh-tw/go.mdx | 63 ++++++++++--------- 37 files changed, 708 insertions(+), 576 deletions(-) diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index a7883cfe4..5c0919e8e 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -249,7 +249,7 @@ export const dict = { "go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع", "go.meta.description": - "يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.", + "يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 وDeepSeek V4 Pro وDeepSeek V4 Flash.", "go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع", "go.hero.body": "يجلب Go البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر، حتى تتمكن من البناء باستخدام وكلاء أقوياء دون القلق بشأن التكلفة أو التوفر.", @@ -300,7 +300,7 @@ export const dict = { "go.problem.item2": "حدود سخية ووصول موثوق", "go.problem.item3": "مصمم لأكبر عدد ممكن من المبرمجين", "go.problem.item4": - "يتضمن GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7", + "يتضمن GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 وDeepSeek V4 Pro وDeepSeek V4 Flash", "go.how.title": "كيف يعمل Go", "go.how.body": "يبدأ Go من $5 للشهر الأول، ثم $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.", "go.how.step1.title": "أنشئ حسابًا", @@ -324,7 +324,7 @@ export const dict = { "go.faq.a2": "يتضمن Go النماذج المدرجة أدناه، مع حدود سخية وإتاحة موثوقة.", "go.faq.q3": "هل Go هو نفسه Zen؟", "go.faq.a3": - "لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7.", + "لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 وDeepSeek V4 Pro وDeepSeek V4 Flash.", "go.faq.q4": "كم تكلفة Go؟", "go.faq.a4.p1.beforePricing": "تكلفة Go", "go.faq.a4.p1.pricingLink": "$5 للشهر الأول", @@ -347,7 +347,7 @@ export const dict = { "go.faq.q9": "ما الفرق بين النماذج المجانية وGo؟", "go.faq.a9": - "تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).", + "تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وKimi K2.6 وMiMo-V2-Pro وMiMo-V2-Omni وMiMo-V2.5-Pro وMiMo-V2.5 وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 وDeepSeek V4 Pro وDeepSeek V4 Flash مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).", "zen.api.error.rateLimitExceeded": "تم تجاوز حد الطلبات. يرجى المحاولة مرة أخرى لاحقًا.", "zen.api.error.modelNotSupported": "النموذج {{model}} غير مدعوم", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index cf7b68d25..76e6987d3 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos", "go.meta.description": - "O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", + "O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash.", "go.hero.title": "Modelos de codificação de baixo custo para todos", "go.hero.body": "O Go traz a codificação com agentes para programadores em todo o mundo. Oferecendo limites generosos e acesso confiável aos modelos de código aberto mais capazes, para que você possa construir com agentes poderosos sem se preocupar com custos ou disponibilidade.", @@ -305,7 +305,7 @@ export const dict = { "go.problem.item2": "Limites generosos e acesso confiável", "go.problem.item3": "Feito para o maior número possível de programadores", "go.problem.item4": - "Inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7", + "Inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash", "go.how.title": "Como o Go funciona", "go.how.body": "O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.", @@ -331,7 +331,7 @@ export const dict = { "go.faq.a2": "O Go inclui os modelos listados abaixo, com limites generosos e acesso confiável.", "go.faq.q3": "O Go é o mesmo que o Zen?", "go.faq.a3": - "Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", + "Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash.", "go.faq.q4": "Quanto custa o Go?", "go.faq.a4.p1.beforePricing": "O Go custa", "go.faq.a4.p1.pricingLink": "$5 no primeiro mês", @@ -355,7 +355,7 @@ export const dict = { "go.faq.q9": "Qual a diferença entre os modelos gratuitos e o Go?", "go.faq.a9": - "Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).", + "Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go inclui GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).", "zen.api.error.rateLimitExceeded": "Limite de taxa excedido. Por favor, tente novamente mais tarde.", "zen.api.error.modelNotSupported": "Modelo {{model}} não suportado", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 90eff469a..b97ee2cc0 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle", "go.meta.description": - "Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", + "Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash.", "go.hero.title": "Kodningsmodeller til lav pris for alle", "go.hero.body": "Go bringer agentisk kodning til programmører over hele verden. Med generøse grænser og pålidelig adgang til de mest kapable open source-modeller, så du kan bygge med kraftfulde agenter uden at bekymre dig om omkostninger eller tilgængelighed.", @@ -302,7 +302,7 @@ export const dict = { "go.problem.item2": "Generøse grænser og pålidelig adgang", "go.problem.item3": "Bygget til så mange programmører som muligt", "go.problem.item4": - "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7", + "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash", "go.how.title": "Hvordan Go virker", "go.how.body": "Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.", @@ -328,7 +328,7 @@ export const dict = { "go.faq.a2": "Go inkluderer modellerne nedenfor med generøse grænser og pålidelig adgang.", "go.faq.q3": "Er Go det samme som Zen?", "go.faq.a3": - "Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", + "Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash.", "go.faq.q4": "Hvad koster Go?", "go.faq.a4.p1.beforePricing": "Go koster", "go.faq.a4.p1.pricingLink": "$5 første måned", @@ -351,7 +351,7 @@ export const dict = { "go.faq.q9": "Hvad er forskellen på gratis modeller og Go?", "go.faq.a9": - "Gratis modeller inkluderer Big Pickle plus salgsfremmende modeller tilgængelige på det tidspunkt, med en kvote på 200 forespørgsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med højere anmodningskvoter håndhævet over rullende vinduer (5-timers, ugentlig og månedlig), nogenlunde svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (faktiske anmodningstal varierer efter model og brug).", + "Gratis modeller inkluderer Big Pickle plus salgsfremmende modeller tilgængelige på det tidspunkt, med en kvote på 200 forespørgsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash med højere anmodningskvoter håndhævet over rullende vinduer (5-timers, ugentlig og månedlig), nogenlunde svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (faktiske anmodningstal varierer efter model og brug).", "zen.api.error.rateLimitExceeded": "Hastighedsgrænse overskredet. Prøv venligst igen senere.", "zen.api.error.modelNotSupported": "Model {{model}} understøttes ikke", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index af339802f..33b6e1b3d 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle", "go.meta.description": - "Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.", + "Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro und DeepSeek V4 Flash.", "go.hero.title": "Kostengünstige Coding-Modelle für alle", "go.hero.body": "Go bringt Agentic Coding zu Programmierern auf der ganzen Welt. Mit großzügigen Limits und zuverlässigem Zugang zu den leistungsfähigsten Open-Source-Modellen, damit du mit leistungsstarken Agenten entwickeln kannst, ohne dir Gedanken über Kosten oder Verfügbarkeit zu machen.", @@ -304,7 +304,7 @@ export const dict = { "go.problem.item2": "Großzügige Limits und zuverlässiger Zugang", "go.problem.item3": "Für so viele Programmierer wie möglich gebaut", "go.problem.item4": - "Beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7", + "Beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro und DeepSeek V4 Flash", "go.how.title": "Wie Go funktioniert", "go.how.body": "Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.", @@ -330,7 +330,7 @@ export const dict = { "go.faq.a2": "Go umfasst die unten aufgeführten Modelle mit großzügigen Limits und zuverlässigem Zugriff.", "go.faq.q3": "Ist Go dasselbe wie Zen?", "go.faq.a3": - "Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.", + "Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro und DeepSeek V4 Flash.", "go.faq.q4": "Wie viel kostet Go?", "go.faq.a4.p1.beforePricing": "Go kostet", "go.faq.a4.p1.pricingLink": "$5 im ersten Monat", @@ -354,7 +354,7 @@ export const dict = { "go.faq.q9": "Was ist der Unterschied zwischen kostenlosen Modellen und Go?", "go.faq.a9": - "Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7 mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).", + "Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go beinhaltet GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro und DeepSeek V4 Flash mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).", "zen.api.error.rateLimitExceeded": "Ratenlimit überschritten. Bitte versuche es später erneut.", "zen.api.error.modelNotSupported": "Modell {{model}} wird nicht unterstützt", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index f5cc954e5..b6934b94d 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -248,7 +248,7 @@ export const dict = { "go.title": "OpenCode Go | Low cost coding models for everyone", "go.meta.description": - "Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.", + "Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, and DeepSeek V4 Flash.", "go.banner.badge": "3x", "go.banner.text": "Kimi K2.6 gets 3× usage limits through April 27", "go.hero.title": "Low cost coding models for everyone", @@ -298,7 +298,7 @@ export const dict = { "go.problem.item2": "Generous limits and reliable access", "go.problem.item3": "Built for as many programmers as possible", "go.problem.item4": - "Includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7", + "Includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, and DeepSeek V4 Flash", "go.how.title": "How Go works", "go.how.body": "Go starts at $5 for your first month, then $10/month. You can use it with OpenCode or any agent.", "go.how.step1.title": "Create an account", @@ -323,7 +323,7 @@ export const dict = { "go.faq.a2": "Go includes the models listed below, with generous limits and reliable access.", "go.faq.q3": "Is Go the same as Zen?", "go.faq.a3": - "No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.", + "No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, and DeepSeek V4 Flash.", "go.faq.q4": "How much does Go cost?", "go.faq.a4.p1.beforePricing": "Go costs", "go.faq.a4.p1.pricingLink": "$5 first month", @@ -347,7 +347,7 @@ export const dict = { "go.faq.q9": "What is the difference between free models and Go?", "go.faq.a9": - "Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7 with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).", + "Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, and DeepSeek V4 Flash with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).", "zen.api.error.rateLimitExceeded": "Rate limit exceeded. Please try again later.", "zen.api.error.modelNotSupported": "Model {{model}} not supported", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index fb718e054..c5cc71ae1 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -254,7 +254,7 @@ export const dict = { "go.title": "OpenCode Go | Modelos de programación de bajo coste para todos", "go.meta.description": - "Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.", + "Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro y DeepSeek V4 Flash.", "go.hero.title": "Modelos de programación de bajo coste para todos", "go.hero.body": "Go lleva la programación agéntica a programadores de todo el mundo. Ofrece límites generosos y acceso fiable a los modelos de código abierto más capaces, para que puedas crear con agentes potentes sin preocuparte por el coste o la disponibilidad.", @@ -306,7 +306,7 @@ export const dict = { "go.problem.item2": "Límites generosos y acceso fiable", "go.problem.item3": "Creado para tantos programadores como sea posible", "go.problem.item4": - "Incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7", + "Incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro y DeepSeek V4 Flash", "go.how.title": "Cómo funciona Go", "go.how.body": "Go comienza en $5 el primer mes, luego 10 $/mes. Puedes usarlo con OpenCode o cualquier agente.", "go.how.step1.title": "Crear una cuenta", @@ -331,7 +331,7 @@ export const dict = { "go.faq.a2": "Go incluye los modelos que se indican abajo, con límites generosos y acceso confiable.", "go.faq.q3": "¿Es Go lo mismo que Zen?", "go.faq.a3": - "No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.", + "No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro y DeepSeek V4 Flash.", "go.faq.q4": "¿Cuánto cuesta Go?", "go.faq.a4.p1.beforePricing": "Go cuesta", "go.faq.a4.p1.pricingLink": "$5 el primer mes", @@ -355,7 +355,7 @@ export const dict = { "go.faq.q9": "¿Cuál es la diferencia entre los modelos gratuitos y Go?", "go.faq.a9": - "Los modelos gratuitos incluyen Big Pickle más modelos promocionales disponibles en el momento, con una cuota de 200 solicitudes/día. Go incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7 con cuotas de solicitud más altas aplicadas a través de ventanas móviles (5 horas, semanal y mensual), aproximadamente equivalente a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (los recuentos reales de solicitudes varían según el modelo y el uso).", + "Los modelos gratuitos incluyen Big Pickle más modelos promocionales disponibles en el momento, con una cuota de 200 solicitudes/día. Go incluye GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro y DeepSeek V4 Flash con cuotas de solicitud más altas aplicadas a través de ventanas móviles (5 horas, semanal y mensual), aproximadamente equivalente a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (los recuentos reales de solicitudes varían según el modelo y el uso).", "zen.api.error.rateLimitExceeded": "Límite de tasa excedido. Por favor, inténtalo de nuevo más tarde.", "zen.api.error.modelNotSupported": "Modelo {{model}} no soportado", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index 976d93a29..04e6e3bc6 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -255,7 +255,7 @@ export const dict = { "go.title": "OpenCode Go | Modèles de code à faible coût pour tous", "go.meta.description": - "Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites de requêtes généreuses sur 5 heures pour GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.", + "Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites de requêtes généreuses sur 5 heures pour GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro et DeepSeek V4 Flash.", "go.hero.title": "Modèles de code à faible coût pour tous", "go.hero.body": "Go apporte le codage agentique aux programmeurs du monde entier. Offrant des limites généreuses et un accès fiable aux modèles open source les plus capables, pour que vous puissiez construire avec des agents puissants sans vous soucier du coût ou de la disponibilité.", @@ -306,7 +306,7 @@ export const dict = { "go.problem.item2": "Limites généreuses et accès fiable", "go.problem.item3": "Conçu pour autant de programmeurs que possible", "go.problem.item4": - "Inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7", + "Inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro et DeepSeek V4 Flash", "go.how.title": "Comment fonctionne Go", "go.how.body": "Go commence à $5 pour le premier mois, puis 10 $/mois. Vous pouvez l'utiliser avec OpenCode ou n'importe quel agent.", @@ -332,7 +332,7 @@ export const dict = { "go.faq.a2": "Go inclut les modèles ci-dessous, avec des limites généreuses et un accès fiable.", "go.faq.q3": "Est-ce que Go est la même chose que Zen ?", "go.faq.a3": - "Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable aux modèles open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.", + "Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable aux modèles open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro et DeepSeek V4 Flash.", "go.faq.q4": "Combien coûte Go ?", "go.faq.a4.p1.beforePricing": "Go coûte", "go.faq.a4.p1.pricingLink": "$5 le premier mois", @@ -355,7 +355,7 @@ export const dict = { "Oui, vous pouvez utiliser Go avec n'importe quel agent. Suivez les instructions de configuration dans votre agent de code préféré.", "go.faq.q9": "Quelle est la différence entre les modèles gratuits et Go ?", "go.faq.a9": - "Les modèles gratuits incluent Big Pickle ainsi que des modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7 avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalent à 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois (le nombre réel de requêtes varie selon le modèle et l'utilisation).", + "Les modèles gratuits incluent Big Pickle ainsi que des modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go inclut GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro et DeepSeek V4 Flash avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalent à 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois (le nombre réel de requêtes varie selon le modèle et l'utilisation).", "zen.api.error.rateLimitExceeded": "Limite de débit dépassée. Veuillez réessayer plus tard.", "zen.api.error.modelNotSupported": "Modèle {{model}} non pris en charge", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 6069ad73c..13f33bfc3 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Modelli di coding a basso costo per tutti", "go.meta.description": - "Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", + "Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash.", "go.hero.title": "Modelli di coding a basso costo per tutti", "go.hero.body": "Go porta il coding agentico ai programmatori di tutto il mondo. Offrendo limiti generosi e un accesso affidabile ai modelli open source più capaci, in modo da poter costruire con agenti potenti senza preoccuparsi dei costi o della disponibilità.", @@ -302,7 +302,7 @@ export const dict = { "go.problem.item2": "Limiti generosi e accesso affidabile", "go.problem.item3": "Costruito per il maggior numero possibile di programmatori", "go.problem.item4": - "Include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7", + "Include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash", "go.how.title": "Come funziona Go", "go.how.body": "Go inizia a $5 per il primo mese, poi $10/mese. Puoi usarlo con OpenCode o qualsiasi agente.", "go.how.step1.title": "Crea un account", @@ -327,7 +327,7 @@ export const dict = { "go.faq.a2": "Go include i modelli elencati di seguito, con limiti generosi e accesso affidabile.", "go.faq.q3": "Go è lo stesso di Zen?", "go.faq.a3": - "No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e accesso affidabile ai modelli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", + "No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e accesso affidabile ai modelli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash.", "go.faq.q4": "Quanto costa Go?", "go.faq.a4.p1.beforePricing": "Go costa", "go.faq.a4.p1.pricingLink": "$5 il primo mese", @@ -351,7 +351,7 @@ export const dict = { "go.faq.q9": "Qual è la differenza tra i modelli gratuiti e Go?", "go.faq.a9": - "I modelli gratuiti includono Big Pickle più modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a $12 ogni 5 ore, $30 a settimana e $60 al mese (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).", + "I modelli gratuiti includono Big Pickle più modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go include GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro e DeepSeek V4 Flash con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a $12 ogni 5 ore, $30 a settimana e $60 al mese (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).", "zen.api.error.rateLimitExceeded": "Limite di richieste superato. Riprova più tardi.", "zen.api.error.modelNotSupported": "Modello {{model}} non supportato", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index dcf2f9b52..845faebf6 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -250,7 +250,7 @@ export const dict = { "go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル", "go.meta.description": - "Goは最初の月$5、その後$10/月で、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7に対して5時間のゆとりあるリクエスト上限があります。", + "Goは最初の月$5、その後$10/月で、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro、DeepSeek V4 Flashに対して5時間のゆとりあるリクエスト上限があります。", "go.hero.title": "すべての人のための低価格なコーディングモデル", "go.hero.body": "Goは、世界中のプログラマーにエージェント型コーディングをもたらします。最も高性能なオープンソースモデルへの十分な制限と安定したアクセスを提供し、コストや可用性を気にすることなく強力なエージェントで構築できます。", @@ -302,7 +302,7 @@ export const dict = { "go.problem.item2": "十分な制限と安定したアクセス", "go.problem.item3": "できるだけ多くのプログラマーのために構築", "go.problem.item4": - "GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7を含む", + "GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro、DeepSeek V4 Flashを含む", "go.how.title": "Goの仕組み", "go.how.body": "Goは最初の月$5、その後$10/月で始まります。OpenCodeまたは任意のエージェントで使えます。", "go.how.step1.title": "アカウントを作成", @@ -327,7 +327,7 @@ export const dict = { "go.faq.a2": "Go には、十分な利用上限と安定したアクセスを備えた、以下のモデルが含まれます。", "go.faq.q3": "GoはZenと同じですか?", "go.faq.a3": - "いいえ。Zenは従量課金制ですが、Goは最初の月$5、その後$10/月で始まり、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7のオープンソースモデルに対して、ゆとりある上限と信頼できるアクセスを提供します。", + "いいえ。Zenは従量課金制ですが、Goは最初の月$5、その後$10/月で始まり、GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro、DeepSeek V4 Flashのオープンソースモデルに対して、ゆとりある上限と信頼できるアクセスを提供します。", "go.faq.q4": "Goの料金は?", "go.faq.a4.p1.beforePricing": "Goは", "go.faq.a4.p1.pricingLink": "最初の月$5", @@ -351,7 +351,7 @@ export const dict = { "go.faq.q9": "無料モデルとGoの違いは何ですか?", "go.faq.a9": - "無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。GoにはGLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7が含まれ、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。これは概算で5時間あたり$12、週間$30、月間$60相当です(実際のリクエスト数はモデルと使用状況により異なります)。", + "無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。GoにはGLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro、DeepSeek V4 Flashが含まれ、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。これは概算で5時間あたり$12、週間$30、月間$60相当です(実際のリクエスト数はモデルと使用状況により異なります)。", "zen.api.error.rateLimitExceeded": "レート制限を超えました。後でもう一度お試しください。", "zen.api.error.modelNotSupported": "モデル {{model}} はサポートされていません", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index f2a67fbba..7efe563a0 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -247,7 +247,7 @@ export const dict = { "go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델", "go.meta.description": - "Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7에 대해 넉넉한 5시간 요청 한도를 제공합니다.", + "Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, DeepSeek V4 Flash에 대해 넉넉한 5시간 요청 한도를 제공합니다.", "go.hero.title": "모두를 위한 저비용 코딩 모델", "go.hero.body": "Go는 전 세계 프로그래머들에게 에이전트 코딩을 제공합니다. 가장 유능한 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공하므로, 비용이나 가용성 걱정 없이 강력한 에이전트로 빌드할 수 있습니다.", @@ -299,7 +299,7 @@ export const dict = { "go.problem.item2": "넉넉한 한도와 안정적인 액세스", "go.problem.item3": "가능한 한 많은 프로그래머를 위해 제작됨", "go.problem.item4": - "GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 포함", + "GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, DeepSeek V4 Flash 포함", "go.how.title": "Go 작동 방식", "go.how.body": "Go는 첫 달 $5, 이후 $10/월로 시작합니다. OpenCode 또는 어떤 에이전트와도 함께 사용할 수 있습니다.", "go.how.step1.title": "계정 생성", @@ -323,7 +323,7 @@ export const dict = { "go.faq.a2": "Go에는 넉넉한 한도와 안정적인 액세스를 제공하는 아래 모델이 포함됩니다.", "go.faq.q3": "Go는 Zen과 같은가요?", "go.faq.a3": - "아니요. Zen은 종량제인 반면, Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.", + "아니요. Zen은 종량제인 반면, Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, DeepSeek V4 Flash 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.", "go.faq.q4": "Go 비용은 얼마인가요?", "go.faq.a4.p1.beforePricing": "Go 비용은", "go.faq.a4.p1.pricingLink": "첫 달 $5", @@ -346,7 +346,7 @@ export const dict = { "go.faq.q9": "무료 모델과 Go의 차이점은 무엇인가요?", "go.faq.a9": - "무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7를 포함하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 이는 대략 5시간당 $12, 주당 $30, 월 $60에 해당합니다(실제 요청 수는 모델 및 사용량에 따라 다름).", + "무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro, DeepSeek V4 Flash를 포함하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 이는 대략 5시간당 $12, 주당 $30, 월 $60에 해당합니다(실제 요청 수는 모델 및 사용량에 따라 다름).", "zen.api.error.rateLimitExceeded": "속도 제한을 초과했습니다. 나중에 다시 시도해 주세요.", "zen.api.error.modelNotSupported": "{{model}} 모델은 지원되지 않습니다", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index 0207a5776..8948e158b 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Rimelige kodemodeller for alle", "go.meta.description": - "Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", + "Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash.", "go.hero.title": "Rimelige kodemodeller for alle", "go.hero.body": "Go bringer agent-koding til programmerere over hele verden. Med rause grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene, kan du bygge med kraftige agenter uten å bekymre deg for kostnader eller tilgjengelighet.", @@ -302,7 +302,7 @@ export const dict = { "go.problem.item2": "Rause grenser og pålitelig tilgang", "go.problem.item3": "Bygget for så mange programmerere som mulig", "go.problem.item4": - "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7", + "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash", "go.how.title": "Hvordan Go fungerer", "go.how.body": "Go starter på $5 for den første måneden, deretter $10/måned. Du kan bruke det med OpenCode eller hvilken som helst agent.", @@ -328,7 +328,7 @@ export const dict = { "go.faq.a2": "Go inkluderer modellene nedenfor, med høye grenser og pålitelig tilgang.", "go.faq.q3": "Er Go det samme som Zen?", "go.faq.a3": - "Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til åpen kildekode-modellene GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", + "Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til åpen kildekode-modellene GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash.", "go.faq.q4": "Hva koster Go?", "go.faq.a4.p1.beforePricing": "Go koster", "go.faq.a4.p1.pricingLink": "$5 første måned", @@ -352,7 +352,7 @@ export const dict = { "go.faq.q9": "Hva er forskjellen mellom gratis modeller og Go?", "go.faq.a9": - "Gratis modeller inkluderer Big Pickle pluss kampanjemodeller tilgjengelig på det tidspunktet, med en kvote på 200 forespørsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med høyere kvoter håndhevet over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende $12 per 5 timer, $30 per uke og $60 per måned (faktiske forespørselsantall varierer etter modell og bruk).", + "Gratis modeller inkluderer Big Pickle pluss kampanjemodeller tilgjengelig på det tidspunktet, med en kvote på 200 forespørsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro og DeepSeek V4 Flash med høyere kvoter håndhevet over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende $12 per 5 timer, $30 per uke og $60 per måned (faktiske forespørselsantall varierer etter modell og bruk).", "zen.api.error.rateLimitExceeded": "Rate limit overskredet. Vennligst prøv igjen senere.", "zen.api.error.modelNotSupported": "Modell {{model}} støttes ikke", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index 554a2d0aa..f879ed705 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -252,7 +252,7 @@ export const dict = { "go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego", "go.meta.description": - "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.", + "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro i DeepSeek V4 Flash.", "go.hero.title": "Niskokosztowe modele do kodowania dla każdego", "go.hero.body": "Go udostępnia programowanie z agentami programistom na całym świecie. Oferuje hojne limity i niezawodny dostęp do najzdolniejszych modeli open source, dzięki czemu możesz budować za pomocą potężnych agentów, nie martwiąc się o koszty czy dostępność.", @@ -303,7 +303,7 @@ export const dict = { "go.problem.item2": "Hojne limity i niezawodny dostęp", "go.problem.item3": "Stworzony dla jak największej liczby programistów", "go.problem.item4": - "Zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7", + "Zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro i DeepSeek V4 Flash", "go.how.title": "Jak działa Go", "go.how.body": "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc. Możesz go używać z OpenCode lub dowolnym agentem.", @@ -329,7 +329,7 @@ export const dict = { "go.faq.a2": "Go obejmuje poniższe modele z wysokimi limitami i niezawodnym dostępem.", "go.faq.q3": "Czy Go to to samo co Zen?", "go.faq.a3": - "Nie. Zen to model płatności za użycie, podczas gdy Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi limitami i niezawodnym dostępem do modeli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.", + "Nie. Zen to model płatności za użycie, podczas gdy Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi limitami i niezawodnym dostępem do modeli open source GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro i DeepSeek V4 Flash.", "go.faq.q4": "Ile kosztuje Go?", "go.faq.a4.p1.beforePricing": "Go kosztuje", "go.faq.a4.p1.pricingLink": "$5 za pierwszy miesiąc", @@ -353,7 +353,7 @@ export const dict = { "go.faq.q9": "Jaka jest różnica między darmowymi modelami a Go?", "go.faq.a9": - "Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7 z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), w przybliżeniu równoważnymi $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie (rzeczywista liczba zapytań zależy od modelu i użycia).", + "Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go zawiera GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro i DeepSeek V4 Flash z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), w przybliżeniu równoważnymi $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie (rzeczywista liczba zapytań zależy od modelu i użycia).", "zen.api.error.rateLimitExceeded": "Przekroczono limit zapytań. Spróbuj ponownie później.", "zen.api.error.modelNotSupported": "Model {{model}} nie jest obsługiwany", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 1e5013419..9ba36d220 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -255,7 +255,7 @@ export const dict = { "go.title": "OpenCode Go | Недорогие модели для кодинга для всех", "go.meta.description": - "Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.", + "Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro и DeepSeek V4 Flash.", "go.hero.title": "Недорогие модели для кодинга для всех", "go.hero.body": "Go открывает доступ к агентам-программистам разработчикам по всему миру. Предлагая щедрые лимиты и надежный доступ к наиболее способным моделям с открытым исходным кодом, вы можете создавать проекты с мощными агентами, не беспокоясь о затратах или доступности.", @@ -307,7 +307,7 @@ export const dict = { "go.problem.item2": "Щедрые лимиты и надежный доступ", "go.problem.item3": "Создан для максимального числа программистов", "go.problem.item4": - "Включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7", + "Включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro и DeepSeek V4 Flash", "go.how.title": "Как работает Go", "go.how.body": "Go начинается с $5 за первый месяц, затем $10/месяц. Вы можете использовать его с OpenCode или любым агентом.", @@ -333,7 +333,7 @@ export const dict = { "go.faq.a2": "Go включает перечисленные ниже модели с щедрыми лимитами и надежным доступом.", "go.faq.q3": "Go — это то же самое, что и Zen?", "go.faq.a3": - "Нет. Zen - это оплата по мере использования, в то время как Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами и надежным доступом к моделям с открытым исходным кодом GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.", + "Нет. Zen - это оплата по мере использования, в то время как Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами и надежным доступом к моделям с открытым исходным кодом GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro и DeepSeek V4 Flash.", "go.faq.q4": "Сколько стоит Go?", "go.faq.a4.p1.beforePricing": "Go стоит", "go.faq.a4.p1.pricingLink": "$5 за первый месяц", @@ -357,7 +357,7 @@ export const dict = { "go.faq.q9": "В чем разница между бесплатными моделями и Go?", "go.faq.a9": - "Бесплатные модели включают Big Pickle плюс промо-модели, доступные на данный момент, с квотой 200 запросов/день. Go включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7 с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно $12 за 5 часов, $30 в неделю и $60 в месяц (фактическое количество запросов зависит от модели и использования).", + "Бесплатные модели включают Big Pickle плюс промо-модели, доступные на данный момент, с квотой 200 запросов/день. Go включает GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro и DeepSeek V4 Flash с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно $12 за 5 часов, $30 в неделю и $60 в месяц (фактическое количество запросов зависит от модели и использования).", "zen.api.error.rateLimitExceeded": "Превышен лимит запросов. Пожалуйста, попробуйте позже.", "zen.api.error.modelNotSupported": "Модель {{model}} не поддерживается", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index 3a2dc4ba4..01b2b19c3 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -250,7 +250,7 @@ export const dict = { "go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน", "go.meta.description": - "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7", + "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro และ DeepSeek V4 Flash", "go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน", "go.hero.body": "Go นำการเขียนโค้ดแบบเอเจนต์มาสู่นักเขียนโปรแกรมทั่วโลก เสนอขีดจำกัดที่กว้างขวางและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดได้อย่างน่าเชื่อถือ เพื่อให้คุณสามารถสร้างสรรค์ด้วยเอเจนต์ที่ทรงพลังโดยไม่ต้องกังวลเรื่องค่าใช้จ่ายหรือความพร้อมใช้งาน", @@ -300,7 +300,7 @@ export const dict = { "go.problem.item2": "ขีดจำกัดที่กว้างขวางและการเข้าถึงที่เชื่อถือได้", "go.problem.item3": "สร้างขึ้นเพื่อโปรแกรมเมอร์จำนวนมากที่สุดเท่าที่จะเป็นไปได้", "go.problem.item4": - "รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7", + "รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro และ DeepSeek V4 Flash", "go.how.title": "Go ทำงานอย่างไร", "go.how.body": "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน คุณสามารถใช้กับ OpenCode หรือเอเจนต์ใดก็ได้", "go.how.step1.title": "สร้างบัญชี", @@ -325,7 +325,7 @@ export const dict = { "go.faq.a2": "Go รวมโมเดลด้านล่างนี้ พร้อมขีดจำกัดที่มากและการเข้าถึงที่เชื่อถือได้", "go.faq.q3": "Go เหมือนกับ Zen หรือไม่?", "go.faq.a3": - "ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ในขณะที่ Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์ส GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 อย่างเชื่อถือได้", + "ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ในขณะที่ Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์ส GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro และ DeepSeek V4 Flash อย่างเชื่อถือได้", "go.faq.q4": "Go ราคาเท่าไหร่?", "go.faq.a4.p1.beforePricing": "Go ราคา", "go.faq.a4.p1.pricingLink": "$5 เดือนแรก", @@ -348,7 +348,7 @@ export const dict = { "go.faq.q9": "ความแตกต่างระหว่างโมเดลฟรีและ Go คืออะไร?", "go.faq.a9": - "โมเดลฟรีรวมถึง Big Pickle บวกกับโมเดลโปรโมชั่นที่มีให้ในขณะนั้น ด้วยโควต้า 200 คำขอ/วัน Go รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 ที่มีโควต้าคำขอสูงกว่า ซึ่งบังคับใช้ผ่านช่วงเวลาหมุนเวียน (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน (จำนวนคำขอจริงจะแตกต่างกันไปตามโมเดลและการใช้งาน)", + "โมเดลฟรีรวมถึง Big Pickle บวกกับโมเดลโปรโมชั่นที่มีให้ในขณะนั้น ด้วยโควต้า 200 คำขอ/วัน Go รวมถึง GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro และ DeepSeek V4 Flash ที่มีโควต้าคำขอสูงกว่า ซึ่งบังคับใช้ผ่านช่วงเวลาหมุนเวียน (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน (จำนวนคำขอจริงจะแตกต่างกันไปตามโมเดลและการใช้งาน)", "zen.api.error.rateLimitExceeded": "เกินขีดจำกัดอัตราการใช้งาน กรุณาลองใหม่ในภายหลัง", "zen.api.error.modelNotSupported": "ไม่รองรับโมเดล {{model}}", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index e2370f83c..0345277b8 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri", "go.meta.description": - "Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 için cömert 5 saatlik istek limitleri sunar.", + "Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro ve DeepSeek V4 Flash için cömert 5 saatlik istek limitleri sunar.", "go.hero.title": "Herkes için düşük maliyetli kodlama modelleri", "go.hero.body": "Go, dünya çapındaki programcılara ajan tabanlı kodlama getiriyor. En yetenekli açık kaynaklı modellere cömert limitler ve güvenilir erişim sunarak, maliyet veya erişilebilirlik konusunda endişelenmeden güçlü ajanlarla geliştirme yapmanızı sağlar.", @@ -305,7 +305,7 @@ export const dict = { "go.problem.item2": "Cömert limitler ve güvenilir erişim", "go.problem.item3": "Mümkün olduğunca çok programcı için geliştirildi", "go.problem.item4": - "GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 içerir", + "GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro ve DeepSeek V4 Flash içerir", "go.how.title": "Go nasıl çalışır?", "go.how.body": "Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar. OpenCode veya herhangi bir ajanla kullanabilirsiniz.", @@ -331,7 +331,7 @@ export const dict = { "go.faq.a2": "Go, aşağıda listelenen modelleri cömert limitler ve güvenilir erişimle sunar.", "go.faq.q3": "Go, Zen ile aynı mı?", "go.faq.a3": - "Hayır. Zen kullandıkça öde modelidir, Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 açık kaynak modellerine cömert limitler ve güvenilir erişim sunar.", + "Hayır. Zen kullandıkça öde modelidir, Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro ve DeepSeek V4 Flash açık kaynak modellerine cömert limitler ve güvenilir erişim sunar.", "go.faq.q4": "Go ne kadar?", "go.faq.a4.p1.beforePricing": "Go'nun maliyeti", "go.faq.a4.p1.pricingLink": "İlk ay $5", @@ -355,7 +355,7 @@ export const dict = { "go.faq.q9": "Ücretsiz modeller ve Go arasındaki fark nedir?", "go.faq.a9": - "Ücretsiz modeller, günlük 200 istek kotası ile Big Pickle ve o sırada mevcut olan promosyonel modelleri içerir. Go ise GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 modellerini; yuvarlanan pencereler (5 saatlik, haftalık ve aylık) üzerinden uygulanan daha yüksek istek kotalarıyla içerir. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerine eşdeğerdir (gerçek istek sayıları modele ve kullanıma göre değişir).", + "Ücretsiz modeller, günlük 200 istek kotası ile Big Pickle ve o sırada mevcut olan promosyonel modelleri içerir. Go ise GLM-5.1, GLM-5, Kimi K2.5, Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7, DeepSeek V4 Pro ve DeepSeek V4 Flash modellerini; yuvarlanan pencereler (5 saatlik, haftalık ve aylık) üzerinden uygulanan daha yüksek istek kotalarıyla içerir. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerine eşdeğerdir (gerçek istek sayıları modele ve kullanıma göre değişir).", "zen.api.error.rateLimitExceeded": "İstek limiti aşıldı. Lütfen daha sonra tekrar deneyin.", "zen.api.error.modelNotSupported": "{{model}} modeli desteklenmiyor", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index 649384c23..b9300cc87 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -241,7 +241,7 @@ export const dict = { "go.title": "OpenCode Go | 人人可用的低成本编程模型", "go.meta.description": - "Go 首月 $5,之后 $10/月,提供对 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小时充裕请求额度。", + "Go 首月 $5,之后 $10/月,提供对 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash 的 5 小时充裕请求额度。", "go.hero.title": "人人可用的低成本编程模型", "go.hero.body": "Go 将代理编程带给全世界的程序员。提供充裕的限额和对最强大的开源模型的可靠访问,让您可以利用强大的代理进行构建,而无需担心成本或可用性。", @@ -291,7 +291,7 @@ export const dict = { "go.problem.item2": "充裕的限额和可靠的访问", "go.problem.item3": "为尽可能多的程序员打造", "go.problem.item4": - "包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7", + "包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash", "go.how.title": "Go 如何工作", "go.how.body": "Go 起价为首月 $5,之后 $10/月。您可以将其与 OpenCode 或任何代理搭配使用。", "go.how.step1.title": "创建账户", @@ -313,7 +313,7 @@ export const dict = { "go.faq.a2": "Go 包含下方列出的模型,提供充足的限额和可靠的访问。", "go.faq.q3": "Go 和 Zen 一样吗?", "go.faq.a3": - "不。Zen 是按量付费,而 Go 首月 $5,之后 $10/月,提供充裕的额度,并可可靠地访问 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等开源模型。", + "不。Zen 是按量付费,而 Go 首月 $5,之后 $10/月,提供充裕的额度,并可可靠地访问 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash 等开源模型。", "go.faq.q4": "Go 多少钱?", "go.faq.a4.p1.beforePricing": "Go 费用为", "go.faq.a4.p1.pricingLink": "首月 $5", @@ -335,7 +335,7 @@ export const dict = { "go.faq.q9": "免费模型和 Go 之间的区别是什么?", "go.faq.a9": - "免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60(实际请求计数因模型和使用情况而异)。", + "免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 包含 GLM-5.1, GLM-5, Kimi K2.5、Kimi K2.6, MiMo-V2-Pro, MiMo-V2-Omni, MiMo-V2.5-Pro, MiMo-V2.5, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60(实际请求计数因模型和使用情况而异)。", "zen.api.error.rateLimitExceeded": "超出速率限制。请稍后重试。", "zen.api.error.modelNotSupported": "不支持模型 {{model}}", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index 8e93f989e..f129a99d0 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -241,7 +241,7 @@ export const dict = { "go.title": "OpenCode Go | 低成本全民編碼模型", "go.meta.description": - "Go 首月 $5,之後 $10/月,提供對 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小時充裕請求額度。", + "Go 首月 $5,之後 $10/月,提供對 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash 的 5 小時充裕請求額度。", "go.hero.title": "低成本全民編碼模型", "go.hero.body": "Go 將代理編碼帶給全世界的程式設計師。提供寬裕的限額以及對最強大開源模型的穩定存取,讓你可以使用強大的代理進行構建,而無需擔心成本或可用性。", @@ -291,7 +291,7 @@ export const dict = { "go.problem.item2": "寬裕的限額與穩定存取", "go.problem.item3": "專為盡可能多的程式設計師打造", "go.problem.item4": - "包含 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7", + "包含 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 與 DeepSeek V4 Flash", "go.how.title": "Go 如何運作", "go.how.body": "Go 起價為首月 $5,之後 $10/月。您可以將其與 OpenCode 或任何代理搭配使用。", "go.how.step1.title": "建立帳號", @@ -313,7 +313,7 @@ export const dict = { "go.faq.a2": "Go 包含下方列出的模型,提供充足的額度與穩定的存取。", "go.faq.q3": "Go 與 Zen 一樣嗎?", "go.faq.a3": - "不。Zen 是按量付費,而 Go 首月 $5,之後 $10/月,提供充裕的額度,並可可靠地存取 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等開源模型。", + "不。Zen 是按量付費,而 Go 首月 $5,之後 $10/月,提供充裕的額度,並可可靠地存取 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 和 DeepSeek V4 Flash 等開源模型。", "go.faq.q4": "Go 費用是多少?", "go.faq.a4.p1.beforePricing": "Go 費用為", "go.faq.a4.p1.pricingLink": "首月 $5", @@ -335,7 +335,7 @@ export const dict = { "go.faq.q9": "免費模型與 Go 有什麼區別?", "go.faq.a9": - "免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為 200 次請求/天。Go 包括 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7,並在滾動視窗(5 小時、每週和每月)內執行更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60(實際請求數因模型和使用情況而異)。", + "免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為 200 次請求/天。Go 包括 GLM-5.1、GLM-5、Kimi K2.5、Kimi K2.6、MiMo-V2-Pro、MiMo-V2-Omni、MiMo-V2.5-Pro、MiMo-V2.5、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7、DeepSeek V4 Pro 與 DeepSeek V4 Flash,並在滾動視窗(5 小時、每週和每月)內執行更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60(實際請求數因模型和使用情況而異)。", "zen.api.error.rateLimitExceeded": "超出頻率限制。請稍後再試。", "zen.api.error.modelNotSupported": "不支援模型 {{model}}", diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index bfaf2969b..2b169fd50 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -23,8 +23,8 @@ const checkLoggedIn = query(async () => { }, "checkLoggedIn.get") const models = [ - { name: "GLM-5.1", provider: "DeepInfra, Z.ai" }, - { name: "GLM-5", provider: "DeepInfra, Z.ai" }, + { name: "GLM-5.1", provider: "DeepInfra, Fireworks AI, Z.ai" }, + { name: "GLM-5", provider: "DeepInfra, Fireworks AI, Z.ai" }, { name: "Kimi K2.5", provider: "Moonshot AI" }, { name: "Kimi K2.6", provider: "Moonshot AI" }, { name: "MiMo-V2-Pro", provider: "Xiaomi MiMo" }, @@ -35,6 +35,8 @@ const models = [ { name: "Qwen3.6 Plus", provider: "Alibaba Cloud Model Studio" }, { name: "MiniMax M2.7", provider: "MiniMax" }, { name: "MiniMax M2.5", provider: "MiniMax" }, + { name: "DeepSeek V4 Pro", provider: "DeepSeek" }, + { name: "DeepSeek V4 Flash", provider: "DeepSeek" }, ] function LimitsGraph(props: { href: string }) { @@ -63,13 +65,15 @@ function LimitsGraph(props: { href: string }) { { id: "glm-5.1", name: "GLM-5.1", req: 880, d: "100ms" }, { id: "kimi-k2.6", name: "Kimi K2.6 (3x usage)", req: 3450, baseReq: 1150, d: "150ms" }, { id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", req: 1290, d: "150ms" }, + { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", req: 1300, d: "200ms" }, { id: "qwen3.6-plus", name: "Qwen3.6 Plus", req: 3300, d: "280ms" }, { id: "minimax-m2.7", name: "MiniMax M2.7", req: 3400, d: "300ms" }, + { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 7450, d: "340ms" }, { id: "qwen3.5-plus", name: "Qwen3.5 Plus", req: 10200, d: "360ms" }, ] const w = 720 - const h = 270 + const h = 330 const left = 40 const right = 60 const top = 18 @@ -103,7 +107,7 @@ function LimitsGraph(props: { href: string }) { })() const shown = ticks.filter((t) => labels.has(t)) const bh = 8 - const gap = 16 + const gap = 20 const step = bh + gap const sep = bh + 40 const fy = top + 22 diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx index abd417e06..0df181ae1 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx @@ -297,6 +297,8 @@ export function LiteSection() {
  • MiniMax M2.7
  • Qwen3.5 Plus
  • Qwen3.6 Plus
  • +
  • DeepSeek V4 Pro
  • +
  • DeepSeek V4 Flash
  • {i18n.t("workspace.lite.promo.footer")}

    diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 785ea35b6..407c91591 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -65,6 +65,8 @@ OpenCode Go حاليًا في المرحلة التجريبية. - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** قد تتغير قائمة النماذج مع استمرارنا في اختبار نماذج جديدة وإضافتها. @@ -82,25 +84,28 @@ OpenCode Go حاليًا في المرحلة التجريبية. يوضح الجدول أدناه عددًا تقديريًا للطلبات بناءً على أنماط استخدام Go المعتادة: -| Model | الطلبات لكل 5 ساعات | الطلبات في الأسبوع | الطلبات في الشهر | -| ------------- | ------------------- | ------------------ | ---------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | الطلبات لكل 5 ساعات | الطلبات في الأسبوع | الطلبات في الشهر | +| --------------- | ------------------- | ------------------ | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | تستند التقديرات إلى متوسطات أنماط الطلبات المرصودة: - GLM-5/5.1 — ‏700 input، و52,000 cached، و150 output tokens لكل طلب - Kimi K2.5/K2.6 — ‏870 input، و55,000 cached، و200 output tokens لكل طلب +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — ‏300 input، و55,000 cached، و125 output tokens لكل طلب - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -129,20 +134,22 @@ OpenCode Go حاليًا في المرحلة التجريبية. يمكنك أيضًا الوصول إلى نماذج Go عبر نقاط نهاية API التالية. -| Model | Model ID | Endpoint | AI SDK Package | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Model | Model ID | Endpoint | AI SDK Package | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | يستخدم [model id](/docs/config/#models) في إعدادات OpenCode لديك التنسيق `opencode-go/`. على سبيل المثال، بالنسبة إلى Kimi K2.6، ستستخدم `opencode-go/kimi-k2.6` في إعداداتك. diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index 523f1ef8e..a3f71ef14 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -75,6 +75,8 @@ Trenutna lista modela uključuje: - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** Lista modela se može mijenjati dok testiramo i dodajemo nove. @@ -92,25 +94,28 @@ Ograničenja su definisana u dolarskoj vrijednosti. To znači da vaš stvarni br Tabela ispod pruža procijenjeni broj zahtjeva na osnovu tipičnih obrazaca korištenja Go pretplate: -| Model | zahtjeva na 5 sati | zahtjeva sedmično | zahtjeva mjesečno | -| ------------- | ------------------ | ----------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | zahtjeva na 5 sati | zahtjeva sedmično | zahtjeva mjesečno | +| --------------- | ------------------ | ----------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Procjene se zasnivaju na zapaženim prosječnim obrascima zahtjeva: - GLM-5/5.1 — 700 ulaznih (input), 52,000 keširanih, 150 izlaznih (output) tokena po zahtjevu - Kimi K2.5/K2.6 — 870 ulaznih, 55,000 keširanih, 200 izlaznih tokena po zahtjevu +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 ulaznih, 55,000 keširanih, 125 izlaznih tokena po zahtjevu - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -141,20 +146,22 @@ nakon što dostignete ograničenja upotrebe umjesto blokiranja zahtjeva. Također možete pristupiti Go modelima putem sljedećih API endpointa. -| Model | Model ID | Endpoint | AI SDK Paket | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Model | Model ID | Endpoint | AI SDK Paket | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [Model id](/docs/config/#models) u vašoj OpenCode konfiguraciji koristi format `opencode-go/`. Na primjer, za Kimi K2.6, koristili biste diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 86a834b98..947c61333 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -75,6 +75,8 @@ Den nuværende liste over modeller inkluderer: - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** Listen over modeller kan ændre sig, efterhånden som vi tester og tilføjer nye. @@ -92,25 +94,28 @@ Grænserne er defineret i dollarværdi. Det betyder, at dit faktiske antal anmod Tabellen nedenfor giver et estimeret antal anmodninger baseret på typiske Go-forbrugsmønstre: -| Model | anmodninger pr. 5 timer | anmodninger pr. uge | anmodninger pr. måned | -| ------------- | ----------------------- | ------------------- | --------------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | anmodninger pr. 5 timer | anmodninger pr. uge | anmodninger pr. måned | +| ----------------- | ----------------------- | ------------------- | --------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Estimaterne er baseret på observerede gennemsnitlige anmodningsmønstre: - GLM-5/5.1 — 700 input, 52.000 cachelagrede, 150 output-tokens pr. anmodning - Kimi K2.5/K2.6 — 870 input, 55.000 cachelagrede, 200 output-tokens pr. anmodning +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 input, 55.000 cachelagrede, 125 output-tokens pr. anmodning - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -141,20 +146,22 @@ når du har nået dine forbrugsgrænser, i stedet for at blokere anmodninger. Du kan også få adgang til Go-modeller gennem følgende API-endpoints. -| Model | Model ID | Endpoint | AI SDK Package | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Model | Model ID | Endpoint | AI SDK Package | +| ----------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Dit [model id](/docs/config/#models) i din OpenCode config bruger formatet `opencode-go/`. For eksempel for Kimi K2.6, vil du diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index 49c0efda5..d78df0195 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -67,6 +67,8 @@ Die aktuelle Liste der Modelle umfasst: - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** Die Liste der Modelle kann sich ändern, während wir neue testen und hinzufügen. @@ -84,25 +86,28 @@ Limits sind in Dollarwerten definiert. Das bedeutet, dass die tatsächliche Anza Die folgende Tabelle zeigt eine geschätzte Anzahl von Anfragen basierend auf typischen Go-Nutzungsmustern: -| Model | Anfragen pro 5 Stunden | Anfragen pro Woche | Anfragen pro Monat | -| ------------- | ---------------------- | ------------------ | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | Anfragen pro 5 Stunden | Anfragen pro Woche | Anfragen pro Monat | +| --------------- | ---------------------- | ------------------ | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Die Schätzungen basieren auf beobachteten durchschnittlichen Anfragemustern: - GLM-5/5.1 — 700 Input-, 52.000 Cached-, 150 Output-Tokens pro Anfrage - Kimi K2.5/K2.6 — 870 Input-, 55.000 Cached-, 200 Output-Tokens pro Anfrage +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 Input-, 55.000 Cached-, 125 Output-Tokens pro Anfrage - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -131,20 +136,22 @@ Wenn du auch Guthaben auf deinem Zen-Konto hast, kannst du in der Console die Op Du kannst auf die Go-Modelle auch über die folgenden API-Endpunkte zugreifen. -| Modell | Modell-ID | Endpunkt | AI SDK Package | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Modell | Modell-ID | Endpunkt | AI SDK Package | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Die [Modell-ID](/docs/config/#models) in deiner OpenCode Config verwendet das Format `opencode-go/`. Für Kimi K2.6 würdest du beispielsweise `opencode-go/kimi-k2.6` in deiner Config verwenden. diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index a541171ca..6d01cfd7f 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -75,6 +75,8 @@ La lista actual de modelos incluye: - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** La lista de modelos puede cambiar a medida que probamos y agregamos otros nuevos. @@ -92,25 +94,28 @@ Los límites se definen en valor en dólares. Esto significa que tu cantidad rea La siguiente tabla proporciona una cantidad estimada de peticiones basada en los patrones típicos de uso de Go: -| Model | peticiones por 5 horas | peticiones por semana | peticiones por mes | -| ------------- | ---------------------- | --------------------- | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | peticiones por 5 horas | peticiones por semana | peticiones por mes | +| --------------- | ---------------------- | --------------------- | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Las estimaciones se basan en los patrones de peticiones promedio observados: - GLM-5/5.1 — 700 tokens de entrada, 52,000 en caché, 150 tokens de salida por petición - Kimi K2.5/K2.6 — 870 tokens de entrada, 55,000 en caché, 200 tokens de salida por petición +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 tokens de entrada, 55,000 en caché, 125 tokens de salida por petición - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -141,20 +146,22 @@ después de que hayas alcanzado tus límites de uso en lugar de bloquear las pet También puedes acceder a los modelos de Go a través de los siguientes endpoints de la API. -| Modelo | ID del modelo | Endpoint | Paquete de AI SDK | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Modelo | ID del modelo | Endpoint | Paquete de AI SDK | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | El [ID del modelo](/docs/config/#models) en tu configuración de OpenCode usa el formato `opencode-go/`. Por ejemplo, para Kimi K2.6, usarías diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 5f55128ed..c4d70e610 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -65,6 +65,8 @@ La liste actuelle des modèles comprend : - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** La liste des modèles peut changer au fur et à mesure que nous en testons et en ajoutons de nouveaux. @@ -82,25 +84,28 @@ Les limites sont définies en valeur monétaire (dollars). Cela signifie que vot Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur des modèles d'utilisation typiques de Go : -| Model | requêtes par 5 heures | requêtes par semaine | requêtes par mois | -| ------------- | --------------------- | -------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | requêtes par 5 heures | requêtes par semaine | requêtes par mois | +| --------------- | --------------------- | -------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Les estimations sont basées sur les modèles de requêtes moyens observés : - GLM-5/5.1 — 700 tokens en entrée, 52,000 en cache, 150 tokens en sortie par requête - Kimi K2.5/K2.6 — 870 tokens en entrée, 55,000 en cache, 200 tokens en sortie par requête +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 tokens en entrée, 55,000 en cache, 125 tokens en sortie par requête - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -129,20 +134,22 @@ Si vous avez également des crédits sur votre solde Zen, vous pouvez activer l' Vous pouvez également accéder aux modèles Go via les points de terminaison d'API suivants. -| Modèle | ID de modèle | Point de terminaison | Package AI SDK | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Modèle | ID de modèle | Point de terminaison | Package AI SDK | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | L'[ID de modèle](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode-go/`. Par exemple, pour Kimi K2.6, vous utiliseriez `opencode-go/kimi-k2.6` dans votre configuration. diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index 946c70de3..9db12a644 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -75,6 +75,8 @@ The current list of models includes: - **MiniMax M2.7** - **Qwen3.5 Plus** - **Qwen3.6 Plus** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** The list of models may change as we test and add new ones. @@ -92,25 +94,28 @@ Limits are defined in dollar value. This means your actual request count depends The table below provides an estimated request count based on typical Go usage patterns: -| Model | requests per 5 hour | requests per week | requests per month | -| ------------- | ------------------- | ----------------- | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | requests per 5 hour | requests per week | requests per month | +| ----------------- | ------------------- | ----------------- | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Estimates are based on observed average request patterns: - GLM-5/5.1 — 700 input, 52,000 cached, 150 output tokens per request - Kimi K2.5/K2.6 — 870 input, 55,000 cached, 200 output tokens per request +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 input, 55,000 cached, 125 output tokens per request - MiMo-V2-Pro — 350 input, 41,000 cached, 250 output tokens per request - MiMo-V2-Omni — 1000 input, 60,000 cached, 140 output tokens per request @@ -141,20 +146,22 @@ after you've reached your usage limits instead of blocking requests. You can also access Go models through the following API endpoints. -| Model | Model ID | Endpoint | AI SDK Package | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Model | Model ID | Endpoint | AI SDK Package | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | The [model id](/docs/config/#models) in your OpenCode config uses the format `opencode-go/`. For example, for Kimi K2.6, you would diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index 341a22c4c..216362ed4 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -73,6 +73,8 @@ L'elenco attuale dei modelli include: - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** L'elenco dei modelli potrebbe cambiare man mano che ne testiamo e aggiungiamo di nuovi. @@ -90,25 +92,28 @@ I limiti sono definiti in valore in dollari. Questo significa che il conteggio e La tabella seguente fornisce una stima del conteggio delle richieste in base a pattern di utilizzo tipici di Go: -| Model | richieste ogni 5 ore | richieste a settimana | richieste al mese | -| ------------- | -------------------- | --------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | richieste ogni 5 ore | richieste a settimana | richieste al mese | +| --------------- | -------------------- | --------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Le stime si basano sui pattern medi di richieste osservati: - GLM-5/5.1 — 700 di input, 52.000 in cache, 150 token di output per richiesta - Kimi K2.5/K2.6 — 870 di input, 55.000 in cache, 200 token di output per richiesta +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 di input, 55.000 in cache, 125 token di output per richiesta - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -139,20 +144,22 @@ dopo che avrai raggiunto i limiti di utilizzo invece di bloccare le richieste. Puoi anche accedere ai modelli Go tramite i seguenti endpoint API. -| Modello | ID Modello | Endpoint | Pacchetto AI SDK | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Modello | ID Modello | Endpoint | Pacchetto AI SDK | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Il [model id](/docs/config/#models) nella tua OpenCode config utilizza il formato `opencode-go/`. Ad esempio, per Kimi K2.6, useresti diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index ddd5a6680..3886a827a 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -65,6 +65,8 @@ OpenCode Goをサブスクライブできるのは、1つのワークスペー - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** 新しいモデルをテストして追加するにつれて、モデルのリストは変更される場合があります。 @@ -82,25 +84,28 @@ OpenCode Goには以下の制限が含まれています: 以下の表は、一般的なGoの利用パターンに基づいた推定リクエスト数を示しています: -| Model | 5時間あたりのリクエスト数 | 週間リクエスト数 | 月間リクエスト数 | -| ------------- | ------------------------- | ---------------- | ---------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | 5時間あたりのリクエスト数 | 週間リクエスト数 | 月間リクエスト数 | +| --------------- | ------------------------- | ---------------- | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | 推定値は、観測された平均的なリクエストパターンに基づいています: - GLM-5/5.1 — リクエストあたり 入力 700トークン、キャッシュ 52,000トークン、出力 150トークン - Kimi K2.5/K2.6 — リクエストあたり 入力 870トークン、キャッシュ 55,000トークン、出力 200トークン +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — リクエストあたり 入力 300トークン、キャッシュ 55,000トークン、出力 125トークン - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -129,20 +134,22 @@ Zen残高にクレジットがある場合は、コンソールで**Use balance* 以下のAPIエンドポイントを通じて、Goモデルにアクセスすることもできます。 -| Model | Model ID | Endpoint | AI SDK Package | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Model | Model ID | Endpoint | AI SDK Package | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode設定の[model id](/docs/config/#models)は、`opencode-go/`という形式を使用します。たとえば、Kimi K2.6の場合は、設定で`opencode-go/kimi-k2.6`を使用します。 diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index da787040f..c0fbc62bd 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -65,6 +65,8 @@ workspace당 한 명의 멤버만 OpenCode Go를 구독할 수 있습니다. - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** 새로운 모델을 테스트하고 추가함에 따라 이 목록은 변경될 수 있습니다. @@ -82,25 +84,28 @@ OpenCode Go에는 다음과 같은 한도가 포함됩니다. 아래 표는 일반적인 Go 사용 패턴을 기준으로 한 예상 요청 횟수를 보여줍니다. -| Model | 5시간당 요청 횟수 | 주간 요청 횟수 | 월간 요청 횟수 | -| ------------- | ----------------- | -------------- | -------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | 5시간당 요청 횟수 | 주간 요청 횟수 | 월간 요청 횟수 | +| --------------- | ----------------- | -------------- | -------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | 예상치는 관찰된 평균 요청 패턴을 기준으로 합니다. - GLM-5/5.1 — 요청당 입력 700, 캐시 52,000, 출력 토큰 150 - Kimi K2.5/K2.6 — 요청당 입력 870, 캐시 55,000, 출력 토큰 200 +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 요청당 입력 300, 캐시 55,000, 출력 토큰 125 - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -129,20 +134,22 @@ Zen 잔액에 크레딧도 있다면, console에서 **Use balance** 옵션을 다음 API 엔드포인트를 통해서도 Go 모델에 액세스할 수 있습니다. -| 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | +| --------------- | -------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode config의 [model id](/docs/config/#models)는 `opencode-go/` 형식을 사용합니다. 예를 들어 Kimi K2.6의 경우 config에서 `opencode-go/kimi-k2.6`를 사용하면 됩니다. diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index 95c05417c..0cae0c57f 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -75,6 +75,8 @@ Den nåværende listen over modeller inkluderer: - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** Listen over modeller kan endres etter hvert som vi tester og legger til nye. @@ -92,25 +94,28 @@ Grensene er definert i dollarverdi. Dette betyr at ditt faktiske antall forespø Tabellen nedenfor gir et estimert antall forespørsler basert på typiske bruksmønstre for Go: -| Model | forespørsler per 5 timer | forespørsler per uke | forespørsler per måned | -| ------------- | ------------------------ | -------------------- | ---------------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | forespørsler per 5 timer | forespørsler per uke | forespørsler per måned | +| --------------- | ------------------------ | -------------------- | ---------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Estimatene er basert på observerte gjennomsnittlige forespørselsmønstre: - GLM-5/5.1 — 700 input, 52 000 bufret, 150 output-tokens per forespørsel - Kimi K2.5/K2.6 — 870 input, 55 000 bufret, 200 output-tokens per forespørsel +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 input, 55 000 bufret, 125 output-tokens per forespørsel - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -141,20 +146,22 @@ etter at du har nådd bruksgrensene dine, i stedet for å blokkere forespørsler Du kan også få tilgang til Go-modeller gjennom følgende API-endepunkter. -| Modell | Modell-ID | Endepunkt | AI SDK Package | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Modell | Modell-ID | Endepunkt | AI SDK Package | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [Modell-ID-en](/docs/config/#models) i din OpenCode-konfigurasjon bruker formatet `opencode-go/`. For eksempel, for Kimi K2.6, vil du diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index 9ae3ea34b..c64075a3b 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -69,6 +69,8 @@ Obecna lista modeli obejmuje: - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** Lista modeli może ulec zmianie w miarę testowania i dodawania nowych. @@ -86,25 +88,28 @@ Limity są zdefiniowane w wartości w dolarach. Oznacza to, że rzeczywista licz Poniższa tabela przedstawia szacunkową liczbę żądań na podstawie typowych wzorców korzystania z Go: -| Model | żądania na 5 godzin | żądania na tydzień | żądania na miesiąc | -| ------------- | ------------------- | ------------------ | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | żądania na 5 godzin | żądania na tydzień | żądania na miesiąc | +| --------------- | ------------------- | ------------------ | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Szacunki opierają się na zaobserwowanych średnich wzorcach żądań: - GLM-5/5.1 — 700 tokenów wejściowych, 52 000 w pamięci podręcznej, 150 tokenów wyjściowych na żądanie - Kimi K2.5/K2.6 — 870 tokenów wejściowych, 55 000 w pamięci podręcznej, 200 tokenów wyjściowych na żądanie +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 tokenów wejściowych, 55 000 w pamięci podręcznej, 125 tokenów wyjściowych na żądanie - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -133,20 +138,22 @@ Jeśli masz również środki na swoim saldzie Zen, możesz włączyć opcję ** Możesz również uzyskać dostęp do modeli Go za pośrednictwem następujących punktów końcowych API. -| Model | ID modelu | Punkt końcowy | Pakiet AI SDK | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Model | ID modelu | Punkt końcowy | Pakiet AI SDK | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [ID modelu](/docs/config/#models) w Twojej konfiguracji OpenCode używa formatu `opencode-go/`. Na przykład dla Kimi K2.6 należy użyć diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index 7d4d90ed5..4a454c93e 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -75,6 +75,8 @@ A lista atual de modelos inclui: - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** A lista de modelos pode mudar conforme testamos e adicionamos novos. @@ -92,25 +94,28 @@ Os limites são definidos em valor em dólares. Isso significa que a sua contage A tabela abaixo fornece uma contagem estimada de requisições com base nos padrões típicos de uso do Go: -| Model | requisições por 5 horas | requisições por semana | requisições por mês | -| ------------- | ----------------------- | ---------------------- | ------------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | requisições por 5 horas | requisições por semana | requisições por mês | +| --------------- | ----------------------- | ---------------------- | ------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | As estimativas baseiam-se nos padrões médios de requisições observados: - GLM-5/5.1 — 700 tokens de entrada, 52.000 em cache, 150 tokens de saída por requisição - Kimi K2.5/K2.6 — 870 tokens de entrada, 55.000 em cache, 200 tokens de saída por requisição +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 tokens de entrada, 55.000 em cache, 125 tokens de saída por requisição - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -141,20 +146,22 @@ após você atingir os seus limites de uso em vez de bloquear as requisições. Você também pode acessar os modelos do Go através dos seguintes endpoints de API. -| Modelo | ID do Modelo | Endpoint | Pacote do AI SDK | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Modelo | ID do Modelo | Endpoint | Pacote do AI SDK | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | O [ID do modelo](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode-go/`. Por exemplo, para o Kimi K2.6, você usaria diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index a8d33f296..2656d3659 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -75,6 +75,8 @@ OpenCode Go работает так же, как и любой другой пр - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** Список моделей может меняться по мере того, как мы тестируем и добавляем новые. @@ -92,25 +94,28 @@ OpenCode Go включает следующие лимиты: В таблице ниже приведено примерное количество запросов на основе типичных сценариев использования Go: -| Model | запросов за 5 часов | запросов в неделю | запросов в месяц | -| ------------- | ------------------- | ----------------- | ---------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | запросов за 5 часов | запросов в неделю | запросов в месяц | +| --------------- | ------------------- | ----------------- | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Оценки основаны на наблюдаемых средних показателях запросов: - GLM-5/5.1 — 700 входных, 52,000 кешированных, 150 выходных токенов на запрос - Kimi K2.5/K2.6 — 870 входных, 55,000 кешированных, 200 выходных токенов на запрос +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 входных, 55,000 кешированных, 125 выходных токенов на запрос - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -141,20 +146,22 @@ OpenCode Go включает следующие лимиты: Вы также можете получить доступ к моделям Go через следующие API-эндпоинты. -| Модель | ID модели | Эндпоинт | Пакет AI SDK | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Модель | ID модели | Эндпоинт | Пакет AI SDK | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [ID модели](/docs/config/#models) в вашем конфиге OpenCode использует формат `opencode-go/`. Например, для Kimi K2.6 вам нужно diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index fb0262c95..04d999159 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -65,6 +65,8 @@ OpenCode Go ทำงานเหมือนกับผู้ให้บร - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** รายชื่อโมเดลอาจมีการเปลี่ยนแปลงเมื่อเราทำการทดสอบและเพิ่มโมเดลใหม่ๆ @@ -82,25 +84,28 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: ตารางด้านล่างแสดงจำนวน request โดยประมาณตามรูปแบบการใช้งานปกติของ Go: -| Model | requests ต่อ 5 ชั่วโมง | requests ต่อสัปดาห์ | requests ต่อเดือน | -| ------------- | ---------------------- | ------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | requests ต่อ 5 ชั่วโมง | requests ต่อสัปดาห์ | requests ต่อเดือน | +| ------------------- | ---------------------- | ------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | การประมาณการอ้างอิงจากรูปแบบการใช้งาน request โดยเฉลี่ยที่สังเกตพบ: - GLM-5/5.1 — 700 input, 52,000 cached, 150 output tokens ต่อ request - Kimi K2.5/K2.6 — 870 input, 55,000 cached, 200 output tokens ต่อ request +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 300 input, 55,000 cached, 125 output tokens ต่อ request - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -129,20 +134,22 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: คุณสามารถเข้าถึงโมเดลของ Go ผ่าน API endpoints ต่อไปนี้ได้เช่นกัน -| Model | Model ID | Endpoint | AI SDK Package | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Model | Model ID | Endpoint | AI SDK Package | +| ------------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [model id](/docs/config/#models) ใน OpenCode config ของคุณจะใช้รูปแบบ `opencode-go/` ตัวอย่างเช่น สำหรับ Kimi K2.6 คุณจะใช้ `opencode-go/kimi-k2.6` ใน config ของคุณ diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index 96a1ca3e2..66556701a 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -65,6 +65,8 @@ Mevcut model listesi şunları içerir: - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** Test edip yenilerini ekledikçe model listesi değişebilir. @@ -82,25 +84,28 @@ Limitler dolar değeri üzerinden belirlenmiştir. Bu, gerçek istek sayınızı Aşağıdaki tablo, tipik Go kullanım modellerine dayalı tahmini bir istek sayısı sunmaktadır: -| Model | 5 saatte bir istek | haftalık istek | aylık istek | -| ------------- | ------------------ | -------------- | ----------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | 5 saatte bir istek | haftalık istek | aylık istek | +| --------------- | ------------------ | -------------- | ----------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Tahminler, gözlemlenen ortalama istek modellerine dayanmaktadır: - GLM-5/5.1 — İstek başına 700 girdi, 52.000 önbelleğe alınmış, 150 çıktı token'ı - Kimi K2.5/K2.6 — İstek başına 870 girdi, 55.000 önbelleğe alınmış, 200 çıktı token'ı +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — İstek başına 300 girdi, 55.000 önbelleğe alınmış, 125 çıktı token'ı - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -129,20 +134,22 @@ Eğer Zen bakiyenizde kredileriniz varsa, konsoldan **Bakiye kullan (Use balance Go modellerine aşağıdaki API uç noktaları aracılığıyla da erişebilirsiniz. -| Model | Model ID | Uç Nokta | AI SDK Paketi | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Model | Model ID | Uç Nokta | AI SDK Paketi | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode yapılandırmanızdaki [model id](/docs/config/#models) formatı `opencode-go/` şeklindedir. Örneğin, Kimi K2.6 için yapılandırmanızda `opencode-go/kimi-k2.6` kullanmalısınız. diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index f52f5b572..47caa9558 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -65,6 +65,8 @@ OpenCode Go 的工作方式与 OpenCode 中的其他提供商一样。 - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** 随着我们进行测试和添加新模型,该列表可能会发生变化。 @@ -82,25 +84,28 @@ OpenCode Go 包含以下限制: 下表提供了基于典型 Go 使用模式的预估请求数: -| Model | 每 5 小时请求数 | 每周请求数 | 每月请求数 | -| ------------- | --------------- | ---------- | ---------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | 每 5 小时请求数 | 每周请求数 | 每月请求数 | +| --------------- | --------------- | ---------- | ---------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | 预估值基于观察到的平均请求模式: - GLM-5/5.1 — 每次请求 700 个输入 token,52,000 个缓存 token,150 个输出 token - Kimi K2.5/K2.6 — 每次请求 870 个输入 token,55,000 个缓存 token,200 个输出 token +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiMo-V2-Pro — 每次请求 350 个输入 token,41,000 个缓存 token,250 个输出 token - MiMo-V2-Omni — 每次请求 1000 个输入 token,60,000 个缓存 token,140 个输出 token - MiMo-V2.5-Pro — 每次请求 350 个输入 token,41,000 个缓存 token,250 个输出 token @@ -129,20 +134,22 @@ OpenCode Go 包含以下限制: 你也可以通过以下 API 端点访问 Go 模型。 -| 模型 | 模型 ID | 端点 | AI SDK 包 | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| 模型 | 模型 ID | 端点 | AI SDK 包 | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | 你 OpenCode 配置中的 [模型 ID](/docs/config/#models) 使用 `opencode-go/` 格式。例如,对于 Kimi K2.6,你将在配置中使用 `opencode-go/kimi-k2.6`。 diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index 481c08cec..de871f915 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -65,6 +65,8 @@ OpenCode Go 的運作方式與 OpenCode 中的任何其他供應商相同。 - **Qwen3.5 Plus** - **Qwen3.6 Plus** - **MiniMax M2.7** +- **DeepSeek V4 Pro** +- **DeepSeek V4 Flash** 隨著我們測試並加入新模型,模型清單可能會有所變動。 @@ -82,25 +84,28 @@ OpenCode Go 包含以下限制: 下表提供了基於典型 Go 使用模式的預估請求次數: -| Model | 每 5 小時請求數 | 每週請求數 | 每月請求數 | -| ------------- | --------------- | ---------- | ---------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | 每 5 小時請求數 | 每週請求數 | 每月請求數 | +| --------------- | --------------- | ---------- | ---------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | 預估值是基於觀察到的平均請求模式: - GLM-5/5.1 — 每次請求 700 個輸入 token、52,000 個快取 token、150 個輸出 token - Kimi K2.5/K2.6 — 每次請求 870 個輸入 token、55,000 個快取 token、200 個輸出 token +- DeepSeek V4 Pro/Flash — 700 input, 52,000 cached, 150 output tokens per request - MiniMax M2.7/M2.5 — 每次請求 300 個輸入 token、55,000 個快取 token、125 個輸出 token - Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request - Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request @@ -129,20 +134,22 @@ OpenCode Go 包含以下限制: 您也可以透過以下 API 端點存取 Go 模型。 -| 模型 | 模型 ID | 端點 | AI SDK 套件 | -| ------------- | ------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| 模型 | 模型 ID | 端點 | AI SDK 套件 | +| --------------- | --------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | 您的 OpenCode 設定中的 [model id](/docs/config/#models) 使用 `opencode-go/` 格式。例如,Kimi K2.6 在設定中應使用 `opencode-go/kimi-k2.6`。 From 1220f784feeba8421d2b2324feb0d137d341ec86 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 24 Apr 2026 15:48:08 +0000 Subject: [PATCH 124/426] chore: generate --- packages/web/src/content/docs/ar/go.mdx | 58 ++++++++++---------- packages/web/src/content/docs/bs/go.mdx | 60 ++++++++++---------- packages/web/src/content/docs/da/go.mdx | 28 +++++----- packages/web/src/content/docs/de/go.mdx | 62 ++++++++++----------- packages/web/src/content/docs/es/go.mdx | 62 ++++++++++----------- packages/web/src/content/docs/fr/go.mdx | 36 ++++++------ packages/web/src/content/docs/it/go.mdx | 62 ++++++++++----------- packages/web/src/content/docs/ja/go.mdx | 34 ++++++------ packages/web/src/content/docs/ko/go.mdx | 62 ++++++++++----------- packages/web/src/content/docs/nb/go.mdx | 40 +++++++------- packages/web/src/content/docs/pl/go.mdx | 62 ++++++++++----------- packages/web/src/content/docs/pt-br/go.mdx | 62 ++++++++++----------- packages/web/src/content/docs/ru/go.mdx | 62 ++++++++++----------- packages/web/src/content/docs/th/go.mdx | 64 +++++++++++----------- packages/web/src/content/docs/tr/go.mdx | 62 ++++++++++----------- packages/web/src/content/docs/zh-cn/go.mdx | 62 ++++++++++----------- packages/web/src/content/docs/zh-tw/go.mdx | 62 ++++++++++----------- 17 files changed, 470 insertions(+), 470 deletions(-) diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 407c91591..42ba87ffa 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -84,20 +84,20 @@ OpenCode Go حاليًا في المرحلة التجريبية. يوضح الجدول أدناه عددًا تقديريًا للطلبات بناءً على أنماط استخدام Go المعتادة: -| Model | الطلبات لكل 5 ساعات | الطلبات في الأسبوع | الطلبات في الشهر | -| --------------- | ------------------- | ------------------ | ---------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| Model | الطلبات لكل 5 ساعات | الطلبات في الأسبوع | الطلبات في الشهر | +| ----------------- | ------------------- | ------------------ | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | | DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | | DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | @@ -134,22 +134,22 @@ OpenCode Go حاليًا في المرحلة التجريبية. يمكنك أيضًا الوصول إلى نماذج Go عبر نقاط نهاية API التالية. -| Model | Model ID | Endpoint | AI SDK Package | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Model | Model ID | Endpoint | AI SDK Package | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | يستخدم [model id](/docs/config/#models) في إعدادات OpenCode لديك التنسيق `opencode-go/`. على سبيل المثال، بالنسبة إلى Kimi K2.6، ستستخدم `opencode-go/kimi-k2.6` في إعداداتك. diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index a3f71ef14..0d183ba28 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -94,22 +94,22 @@ Ograničenja su definisana u dolarskoj vrijednosti. To znači da vaš stvarni br Tabela ispod pruža procijenjeni broj zahtjeva na osnovu tipičnih obrazaca korištenja Go pretplate: -| Model | zahtjeva na 5 sati | zahtjeva sedmično | zahtjeva mjesečno | -| --------------- | ------------------ | ----------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | zahtjeva na 5 sati | zahtjeva sedmično | zahtjeva mjesečno | +| ----------------- | ------------------ | ----------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Procjene se zasnivaju na zapaženim prosječnim obrascima zahtjeva: @@ -146,22 +146,22 @@ nakon što dostignete ograničenja upotrebe umjesto blokiranja zahtjeva. Također možete pristupiti Go modelima putem sljedećih API endpointa. -| Model | Model ID | Endpoint | AI SDK Paket | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Model | Model ID | Endpoint | AI SDK Paket | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [Model id](/docs/config/#models) u vašoj OpenCode konfiguraciji koristi format `opencode-go/`. Na primjer, za Kimi K2.6, koristili biste diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 947c61333..fcdbaf5f9 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -146,22 +146,22 @@ når du har nået dine forbrugsgrænser, i stedet for at blokere anmodninger. Du kan også få adgang til Go-modeller gennem følgende API-endpoints. -| Model | Model ID | Endpoint | AI SDK Package | -| ----------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Model | Model ID | Endpoint | AI SDK Package | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Dit [model id](/docs/config/#models) i din OpenCode config bruger formatet `opencode-go/`. For eksempel for Kimi K2.6, vil du diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index d78df0195..09341efa4 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -86,22 +86,22 @@ Limits sind in Dollarwerten definiert. Das bedeutet, dass die tatsächliche Anza Die folgende Tabelle zeigt eine geschätzte Anzahl von Anfragen basierend auf typischen Go-Nutzungsmustern: -| Model | Anfragen pro 5 Stunden | Anfragen pro Woche | Anfragen pro Monat | -| --------------- | ---------------------- | ------------------ | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | Anfragen pro 5 Stunden | Anfragen pro Woche | Anfragen pro Monat | +| ----------------- | ---------------------- | ------------------ | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Die Schätzungen basieren auf beobachteten durchschnittlichen Anfragemustern: @@ -136,22 +136,22 @@ Wenn du auch Guthaben auf deinem Zen-Konto hast, kannst du in der Console die Op Du kannst auf die Go-Modelle auch über die folgenden API-Endpunkte zugreifen. -| Modell | Modell-ID | Endpunkt | AI SDK Package | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Modell | Modell-ID | Endpunkt | AI SDK Package | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Die [Modell-ID](/docs/config/#models) in deiner OpenCode Config verwendet das Format `opencode-go/`. Für Kimi K2.6 würdest du beispielsweise `opencode-go/kimi-k2.6` in deiner Config verwenden. diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index 6d01cfd7f..fd1edcf9c 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -94,22 +94,22 @@ Los límites se definen en valor en dólares. Esto significa que tu cantidad rea La siguiente tabla proporciona una cantidad estimada de peticiones basada en los patrones típicos de uso de Go: -| Model | peticiones por 5 horas | peticiones por semana | peticiones por mes | -| --------------- | ---------------------- | --------------------- | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | peticiones por 5 horas | peticiones por semana | peticiones por mes | +| ----------------- | ---------------------- | --------------------- | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Las estimaciones se basan en los patrones de peticiones promedio observados: @@ -146,22 +146,22 @@ después de que hayas alcanzado tus límites de uso en lugar de bloquear las pet También puedes acceder a los modelos de Go a través de los siguientes endpoints de la API. -| Modelo | ID del modelo | Endpoint | Paquete de AI SDK | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Modelo | ID del modelo | Endpoint | Paquete de AI SDK | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | El [ID del modelo](/docs/config/#models) en tu configuración de OpenCode usa el formato `opencode-go/`. Por ejemplo, para Kimi K2.6, usarías diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index c4d70e610..80cd5d2fc 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -84,11 +84,11 @@ Les limites sont définies en valeur monétaire (dollars). Cela signifie que vot Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur des modèles d'utilisation typiques de Go : -| Model | requêtes par 5 heures | requêtes par semaine | requêtes par mois | -| --------------- | --------------------- | -------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Model | requêtes par 5 heures | requêtes par semaine | requêtes par mois | +| ----------------- | --------------------- | -------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | @@ -134,22 +134,22 @@ Si vous avez également des crédits sur votre solde Zen, vous pouvez activer l' Vous pouvez également accéder aux modèles Go via les points de terminaison d'API suivants. -| Modèle | ID de modèle | Point de terminaison | Package AI SDK | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Modèle | ID de modèle | Point de terminaison | Package AI SDK | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | L'[ID de modèle](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode-go/`. Par exemple, pour Kimi K2.6, vous utiliseriez `opencode-go/kimi-k2.6` dans votre configuration. diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index 216362ed4..775e567f8 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -92,22 +92,22 @@ I limiti sono definiti in valore in dollari. Questo significa che il conteggio e La tabella seguente fornisce una stima del conteggio delle richieste in base a pattern di utilizzo tipici di Go: -| Model | richieste ogni 5 ore | richieste a settimana | richieste al mese | -| --------------- | -------------------- | --------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | richieste ogni 5 ore | richieste a settimana | richieste al mese | +| ----------------- | -------------------- | --------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Le stime si basano sui pattern medi di richieste osservati: @@ -144,22 +144,22 @@ dopo che avrai raggiunto i limiti di utilizzo invece di bloccare le richieste. Puoi anche accedere ai modelli Go tramite i seguenti endpoint API. -| Modello | ID Modello | Endpoint | Pacchetto AI SDK | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Modello | ID Modello | Endpoint | Pacchetto AI SDK | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Il [model id](/docs/config/#models) nella tua OpenCode config utilizza il formato `opencode-go/`. Ad esempio, per Kimi K2.6, useresti diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index 3886a827a..6ab9c3235 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -84,8 +84,8 @@ OpenCode Goには以下の制限が含まれています: 以下の表は、一般的なGoの利用パターンに基づいた推定リクエスト数を示しています: -| Model | 5時間あたりのリクエスト数 | 週間リクエスト数 | 月間リクエスト数 | -| --------------- | ------------------------- | ---------------- | ---------------- | +| Model | 5時間あたりのリクエスト数 | 週間リクエスト数 | 月間リクエスト数 | +| ----------------- | ------------------------- | ---------------- | ---------------- | | GLM-5.1 | 880 | 2,150 | 4,300 | | GLM-5 | 1,150 | 2,880 | 5,750 | | Kimi K2.5 | 1,850 | 4,630 | 9,250 | @@ -134,22 +134,22 @@ Zen残高にクレジットがある場合は、コンソールで**Use balance* 以下のAPIエンドポイントを通じて、Goモデルにアクセスすることもできます。 -| Model | Model ID | Endpoint | AI SDK Package | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Model | Model ID | Endpoint | AI SDK Package | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode設定の[model id](/docs/config/#models)は、`opencode-go/`という形式を使用します。たとえば、Kimi K2.6の場合は、設定で`opencode-go/kimi-k2.6`を使用します。 diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index c0fbc62bd..83c7c8e6a 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -84,22 +84,22 @@ OpenCode Go에는 다음과 같은 한도가 포함됩니다. 아래 표는 일반적인 Go 사용 패턴을 기준으로 한 예상 요청 횟수를 보여줍니다. -| Model | 5시간당 요청 횟수 | 주간 요청 횟수 | 월간 요청 횟수 | -| --------------- | ----------------- | -------------- | -------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | 5시간당 요청 횟수 | 주간 요청 횟수 | 월간 요청 횟수 | +| ----------------- | ----------------- | -------------- | -------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | 예상치는 관찰된 평균 요청 패턴을 기준으로 합니다. @@ -134,22 +134,22 @@ Zen 잔액에 크레딧도 있다면, console에서 **Use balance** 옵션을 다음 API 엔드포인트를 통해서도 Go 모델에 액세스할 수 있습니다. -| 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | -| --------------- | -------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode config의 [model id](/docs/config/#models)는 `opencode-go/` 형식을 사용합니다. 예를 들어 Kimi K2.6의 경우 config에서 `opencode-go/kimi-k2.6`를 사용하면 됩니다. diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index 0cae0c57f..53ad74f37 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -94,11 +94,11 @@ Grensene er definert i dollarverdi. Dette betyr at ditt faktiske antall forespø Tabellen nedenfor gir et estimert antall forespørsler basert på typiske bruksmønstre for Go: -| Model | forespørsler per 5 timer | forespørsler per uke | forespørsler per måned | -| --------------- | ------------------------ | -------------------- | ---------------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Model | forespørsler per 5 timer | forespørsler per uke | forespørsler per måned | +| ----------------- | ------------------------ | -------------------- | ---------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | | Kimi K2.6 | 1,150 | 2,880 | 5,750 | | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | @@ -146,22 +146,22 @@ etter at du har nådd bruksgrensene dine, i stedet for å blokkere forespørsler Du kan også få tilgang til Go-modeller gjennom følgende API-endepunkter. -| Modell | Modell-ID | Endepunkt | AI SDK Package | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Modell | Modell-ID | Endepunkt | AI SDK Package | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [Modell-ID-en](/docs/config/#models) i din OpenCode-konfigurasjon bruker formatet `opencode-go/`. For eksempel, for Kimi K2.6, vil du diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index c64075a3b..87fcf1cc3 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -88,22 +88,22 @@ Limity są zdefiniowane w wartości w dolarach. Oznacza to, że rzeczywista licz Poniższa tabela przedstawia szacunkową liczbę żądań na podstawie typowych wzorców korzystania z Go: -| Model | żądania na 5 godzin | żądania na tydzień | żądania na miesiąc | -| --------------- | ------------------- | ------------------ | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | żądania na 5 godzin | żądania na tydzień | żądania na miesiąc | +| ----------------- | ------------------- | ------------------ | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Szacunki opierają się na zaobserwowanych średnich wzorcach żądań: @@ -138,22 +138,22 @@ Jeśli masz również środki na swoim saldzie Zen, możesz włączyć opcję ** Możesz również uzyskać dostęp do modeli Go za pośrednictwem następujących punktów końcowych API. -| Model | ID modelu | Punkt końcowy | Pakiet AI SDK | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Model | ID modelu | Punkt końcowy | Pakiet AI SDK | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [ID modelu](/docs/config/#models) w Twojej konfiguracji OpenCode używa formatu `opencode-go/`. Na przykład dla Kimi K2.6 należy użyć diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index 4a454c93e..4a2208933 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -94,22 +94,22 @@ Os limites são definidos em valor em dólares. Isso significa que a sua contage A tabela abaixo fornece uma contagem estimada de requisições com base nos padrões típicos de uso do Go: -| Model | requisições por 5 horas | requisições por semana | requisições por mês | -| --------------- | ----------------------- | ---------------------- | ------------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | requisições por 5 horas | requisições por semana | requisições por mês | +| ----------------- | ----------------------- | ---------------------- | ------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | As estimativas baseiam-se nos padrões médios de requisições observados: @@ -146,22 +146,22 @@ após você atingir os seus limites de uso em vez de bloquear as requisições. Você também pode acessar os modelos do Go através dos seguintes endpoints de API. -| Modelo | ID do Modelo | Endpoint | Pacote do AI SDK | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Modelo | ID do Modelo | Endpoint | Pacote do AI SDK | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | O [ID do modelo](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode-go/`. Por exemplo, para o Kimi K2.6, você usaria diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index 2656d3659..5a07418a7 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -94,22 +94,22 @@ OpenCode Go включает следующие лимиты: В таблице ниже приведено примерное количество запросов на основе типичных сценариев использования Go: -| Model | запросов за 5 часов | запросов в неделю | запросов в месяц | -| --------------- | ------------------- | ----------------- | ---------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | запросов за 5 часов | запросов в неделю | запросов в месяц | +| ----------------- | ------------------- | ----------------- | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Оценки основаны на наблюдаемых средних показателях запросов: @@ -146,22 +146,22 @@ OpenCode Go включает следующие лимиты: Вы также можете получить доступ к моделям Go через следующие API-эндпоинты. -| Модель | ID модели | Эндпоинт | Пакет AI SDK | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Модель | ID модели | Эндпоинт | Пакет AI SDK | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [ID модели](/docs/config/#models) в вашем конфиге OpenCode использует формат `opencode-go/`. Например, для Kimi K2.6 вам нужно diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index 04d999159..17cd9feb8 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -84,22 +84,22 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: ตารางด้านล่างแสดงจำนวน request โดยประมาณตามรูปแบบการใช้งานปกติของ Go: -| Model | requests ต่อ 5 ชั่วโมง | requests ต่อสัปดาห์ | requests ต่อเดือน | -| ------------------- | ---------------------- | ------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | requests ต่อ 5 ชั่วโมง | requests ต่อสัปดาห์ | requests ต่อเดือน | +| ----------------- | ---------------------- | ------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | การประมาณการอ้างอิงจากรูปแบบการใช้งาน request โดยเฉลี่ยที่สังเกตพบ: @@ -134,22 +134,22 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: คุณสามารถเข้าถึงโมเดลของ Go ผ่าน API endpoints ต่อไปนี้ได้เช่นกัน -| Model | Model ID | Endpoint | AI SDK Package | -| ------------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Model | Model ID | Endpoint | AI SDK Package | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [model id](/docs/config/#models) ใน OpenCode config ของคุณจะใช้รูปแบบ `opencode-go/` ตัวอย่างเช่น สำหรับ Kimi K2.6 คุณจะใช้ `opencode-go/kimi-k2.6` ใน config ของคุณ diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index 66556701a..884c0e00c 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -84,22 +84,22 @@ Limitler dolar değeri üzerinden belirlenmiştir. Bu, gerçek istek sayınızı Aşağıdaki tablo, tipik Go kullanım modellerine dayalı tahmini bir istek sayısı sunmaktadır: -| Model | 5 saatte bir istek | haftalık istek | aylık istek | -| --------------- | ------------------ | -------------- | ----------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | 5 saatte bir istek | haftalık istek | aylık istek | +| ----------------- | ------------------ | -------------- | ----------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Tahminler, gözlemlenen ortalama istek modellerine dayanmaktadır: @@ -134,22 +134,22 @@ Eğer Zen bakiyenizde kredileriniz varsa, konsoldan **Bakiye kullan (Use balance Go modellerine aşağıdaki API uç noktaları aracılığıyla da erişebilirsiniz. -| Model | Model ID | Uç Nokta | AI SDK Paketi | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Model | Model ID | Uç Nokta | AI SDK Paketi | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode yapılandırmanızdaki [model id](/docs/config/#models) formatı `opencode-go/` şeklindedir. Örneğin, Kimi K2.6 için yapılandırmanızda `opencode-go/kimi-k2.6` kullanmalısınız. diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index 47caa9558..828da590a 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -84,22 +84,22 @@ OpenCode Go 包含以下限制: 下表提供了基于典型 Go 使用模式的预估请求数: -| Model | 每 5 小时请求数 | 每周请求数 | 每月请求数 | -| --------------- | --------------- | ---------- | ---------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | 每 5 小时请求数 | 每周请求数 | 每月请求数 | +| ----------------- | --------------- | ---------- | ---------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | 预估值基于观察到的平均请求模式: @@ -134,22 +134,22 @@ OpenCode Go 包含以下限制: 你也可以通过以下 API 端点访问 Go 模型。 -| 模型 | 模型 ID | 端点 | AI SDK 包 | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| 模型 | 模型 ID | 端点 | AI SDK 包 | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | 你 OpenCode 配置中的 [模型 ID](/docs/config/#models) 使用 `opencode-go/` 格式。例如,对于 Kimi K2.6,你将在配置中使用 `opencode-go/kimi-k2.6`。 diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index de871f915..1df68a6c4 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -84,22 +84,22 @@ OpenCode Go 包含以下限制: 下表提供了基於典型 Go 使用模式的預估請求次數: -| Model | 每 5 小時請求數 | 每週請求數 | 每月請求數 | -| --------------- | --------------- | ---------- | ---------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | 每 5 小時請求數 | 每週請求數 | 每月請求數 | +| ----------------- | --------------- | ---------- | ---------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | 預估值是基於觀察到的平均請求模式: @@ -134,22 +134,22 @@ OpenCode Go 包含以下限制: 您也可以透過以下 API 端點存取 Go 模型。 -| 模型 | 模型 ID | 端點 | AI SDK 套件 | -| --------------- | --------------- | ------------------------------------------------ | --------------------------- | -| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| 模型 | 模型 ID | 端點 | AI SDK 套件 | +| ----------------- | ----------------- | ------------------------------------------------ | --------------------------- | +| GLM-5.1 | glm-5.1 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | -| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiMo-V2.5 | mimo-v2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | 您的 OpenCode 設定中的 [model id](/docs/config/#models) 使用 `opencode-go/` 格式。例如,Kimi K2.6 在設定中應使用 `opencode-go/kimi-k2.6`。 From 28025a0f36c710a9db70502e04320b2d18b0d8e2 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 24 Apr 2026 15:53:03 +0000 Subject: [PATCH 125/426] sync release versions for v1.14.24 --- bun.lock | 32 +++++++++++++------------- 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-electron/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/shared/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index 359709a46..d42183f93 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -83,7 +83,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -117,7 +117,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -144,7 +144,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -168,7 +168,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -192,7 +192,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -225,7 +225,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -269,7 +269,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -298,7 +298,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -314,7 +314,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.14.23", + "version": "1.14.24", "bin": { "opencode": "./bin/opencode", }, @@ -459,7 +459,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -494,7 +494,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "cross-spawn": "catalog:", }, @@ -509,7 +509,7 @@ }, "packages/shared": { "name": "@opencode-ai/shared", - "version": "1.14.23", + "version": "1.14.24", "bin": { "opencode": "./bin/opencode", }, @@ -533,7 +533,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -568,7 +568,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -617,7 +617,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.14.23", + "version": "1.14.24", "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 b01b9b9d1..324d8058c 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.14.23", + "version": "1.14.24", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index acd7024a6..e553d1d26 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.14.23", + "version": "1.14.24", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index a8ba8c539..09dad6014 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.14.23", + "version": "1.14.24", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index b917ee2b5..2005a941b 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.14.23", + "version": "1.14.24", "$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 17fd13d0f..16d99c7c4 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.14.23", + "version": "1.14.24", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index bd286786b..0c9d8b253 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.14.23", + "version": "1.14.24", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 561a97470..34a1375f6 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.14.23", + "version": "1.14.24", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 11a6ea7ef..13be3723d 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.14.23", + "version": "1.14.24", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 2943614d3..d8c9ecc8c 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.14.23" +version = "1.14.24" 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.14.23/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.23/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/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.14.23/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/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.14.23/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 1501f3df4..5a1e2beb4 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.14.23", + "version": "1.14.24", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index f1d72c640..14ae8b72e 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.23", + "version": "1.14.24", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 45926f05b..e7d9b87ed 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.14.23", + "version": "1.14.24", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 9f9658eb1..d924f1e68 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.14.23", + "version": "1.14.24", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/shared/package.json b/packages/shared/package.json index 08bb00929..151751bda 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.23", + "version": "1.14.24", "name": "@opencode-ai/shared", "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index 5149dbffe..89f54026a 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.14.23", + "version": "1.14.24", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index f4d90f146..6339b1f5b 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.14.23", + "version": "1.14.24", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 386078d17..a8e9d0e0e 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.14.23", + "version": "1.14.24", "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 035a45eb7..217cb037a 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.14.23", + "version": "1.14.24", "publisher": "sst-dev", "repository": { "type": "git", From d01ad4c4992f931e334eaeac1baf7001e9d8f4f7 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 24 Apr 2026 14:05:29 -0400 Subject: [PATCH 126/426] zen: gpt-5.5 --- packages/web/src/content/docs/ar/zen.mdx | 4 +++- packages/web/src/content/docs/bs/zen.mdx | 6 ++++-- packages/web/src/content/docs/da/zen.mdx | 6 ++++-- packages/web/src/content/docs/de/zen.mdx | 4 +++- packages/web/src/content/docs/es/zen.mdx | 6 ++++-- packages/web/src/content/docs/fr/zen.mdx | 4 +++- packages/web/src/content/docs/it/zen.mdx | 6 ++++-- packages/web/src/content/docs/ja/zen.mdx | 6 +++++- packages/web/src/content/docs/ko/zen.mdx | 4 +++- packages/web/src/content/docs/nb/zen.mdx | 2 ++ packages/web/src/content/docs/pl/zen.mdx | 2 ++ packages/web/src/content/docs/pt-br/zen.mdx | 2 ++ packages/web/src/content/docs/ru/zen.mdx | 2 ++ packages/web/src/content/docs/th/zen.mdx | 2 ++ packages/web/src/content/docs/tr/zen.mdx | 2 ++ packages/web/src/content/docs/zen.mdx | 8 ++++++-- packages/web/src/content/docs/zh-cn/zen.mdx | 2 ++ packages/web/src/content/docs/zh-tw/zen.mdx | 2 ++ 18 files changed, 55 insertions(+), 15 deletions(-) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index f85c15ea9..42ab5034c 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -59,6 +59,8 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | النموذج | معرّف النموذج | نقطة النهاية | حزمة AI SDK | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -99,7 +101,7 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -يستخدم [معرّف النموذج](/docs/config/#models) في إعدادات OpenCode الصيغة `opencode/`. على سبيل المثال، بالنسبة إلى GPT 5.4، ستستخدم `opencode/gpt-5.4` في إعداداتك. +يستخدم [معرّف النموذج](/docs/config/#models) في إعدادات OpenCode الصيغة `opencode/`. على سبيل المثال، بالنسبة إلى GPT 5.5، ستستخدم `opencode/gpt-5.5` في إعداداتك. --- diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index 026e145ad..6bc25b4c1 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -64,6 +64,8 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Model | Model ID | Endpoint | AI SDK Package | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -105,8 +107,8 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) u vašoj OpenCode konfiguraciji koristi format -`opencode/`. Na primjer, za GPT 5.4 u konfiguraciji biste -koristili `opencode/gpt-5.4`. +`opencode/`. Na primjer, za GPT 5.5 u konfiguraciji biste +koristili `opencode/gpt-5.5`. --- diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index b5c9f469f..51bae8ef6 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -64,6 +64,8 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Model | Model ID | Endpoint | AI SDK-pakke | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -105,8 +107,8 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) i din OpenCode-konfiguration -bruger formatet `opencode/`. For eksempel ville du for GPT 5.4 -bruge `opencode/gpt-5.4` i din konfiguration. +bruger formatet `opencode/`. For eksempel ville du for GPT 5.5 +bruge `opencode/gpt-5.5` i din konfiguration. --- diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index 2fd012dd1..30a78ddd6 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -55,6 +55,8 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Model | Model ID | Endpoint | AI SDK Package | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -95,7 +97,7 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -Die [Model-ID](/docs/config/#models) in deiner OpenCode-Konfiguration verwendet das Format `opencode/`. Für GPT 5.4 würdest du zum Beispiel `opencode/gpt-5.4` in deiner Konfiguration verwenden. +Die [Model-ID](/docs/config/#models) in deiner OpenCode-Konfiguration verwendet das Format `opencode/`. Für GPT 5.5 würdest du zum Beispiel `opencode/gpt-5.5` in deiner Konfiguration verwenden. --- diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 82ed07a23..9534c61de 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -64,6 +64,8 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Modelo | Model ID | Endpoint | AI SDK Package | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -105,8 +107,8 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | El [identificador del modelo](/docs/config/#models) en tu configuración de OpenCode -usa el formato `opencode/`. Por ejemplo, para GPT 5.4, usarías -`opencode/gpt-5.4` en tu configuración. +usa el formato `opencode/`. Por ejemplo, para GPT 5.5, usarías +`opencode/gpt-5.5` en tu configuración. --- diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index 0b2bcf94a..cd2a621f5 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -55,6 +55,8 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Modèle | ID du modèle | Point de terminaison | Package AI SDK | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -95,7 +97,7 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -Le [model id](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode/`. Par exemple, pour GPT 5.4, vous utiliseriez `opencode/gpt-5.4` dans votre configuration. +Le [model id](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode/`. Par exemple, pour GPT 5.5, vous utiliseriez `opencode/gpt-5.5` dans votre configuration. --- diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 14ddb891d..451d40751 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -64,6 +64,8 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Modello | Model ID | Endpoint | Pacchetto AI SDK | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -105,8 +107,8 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | Il [model id](/docs/config/#models) nella config di OpenCode -usa il formato `opencode/`. Per esempio, per GPT 5.4, useresti -`opencode/gpt-5.4` nella tua config. +usa il formato `opencode/`. Per esempio, per GPT 5.5, useresti +`opencode/gpt-5.5` nella tua config. --- diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index ee84dc917..0d142a73f 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -55,6 +55,8 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Model | Model ID | Endpoint | AI SDK Package | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -95,7 +97,7 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -OpenCode 設定で使う [model id](/docs/config/#models) は `opencode/` 形式です。たとえば、GPT 5.4 では設定に `opencode/gpt-5.4` を使用します。 +OpenCode 設定で使う [model id](/docs/config/#models) は `opencode/` 形式です。たとえば、GPT 5.5 では設定に `opencode/gpt-5.5` を使用します。 --- @@ -142,6 +144,8 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 416ec9f20..1fc661da6 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -55,6 +55,8 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -95,7 +97,7 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -OpenCode config에서 사용하는 [모델 ID](/docs/config/#models)는 `opencode/` 형식입니다. 예를 들어 GPT 5.4를 사용하려면 config에서 `opencode/gpt-5.4`를 사용하면 됩니다. +OpenCode config에서 사용하는 [모델 ID](/docs/config/#models)는 `opencode/` 형식입니다. 예를 들어 GPT 5.5를 사용하려면 config에서 `opencode/gpt-5.5`를 사용하면 됩니다. --- diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index 25689ef8c..6d9834799 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -64,6 +64,8 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Modell | Modell-ID | Endepunkt | AI SDK-pakke | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 023f22da2..0e2ee0af7 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -64,6 +64,8 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Model | ID modelu | Endpoint | Pakiet AI SDK | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index 05fabd40c..73be927b8 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -55,6 +55,8 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Modelo | ID do modelo | Endpoint | Pacote AI SDK | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 13184ecf7..92b08e238 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -64,6 +64,8 @@ OpenCode Zen работает как любой другой провайдер | Модель | Идентификатор модели | Конечная точка | Пакет AI SDK | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index b75e6d5fd..b082bff00 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -57,6 +57,8 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Model | Model ID | Endpoint | AI SDK Package | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index a3a1781f0..bf3d1ac3f 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -55,6 +55,8 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Model | Model ID | Endpoint | AI SDK Package | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index d208d3ae5..97d16618d 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -64,6 +64,8 @@ You can also access our models through the following API endpoints. | Model | Model ID | Endpoint | AI SDK Package | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | @@ -105,8 +107,8 @@ You can also access our models through the following API endpoints. | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | The [model id](/docs/config/#models) in your OpenCode config -uses the format `opencode/`. For example, for GPT 5.4, you would -use `opencode/gpt-5.4` in your config. +uses the format `opencode/`. For example, for GPT 5.5, you would +use `opencode/gpt-5.5` in your config. --- @@ -153,6 +155,8 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index 510b93666..a74386e8b 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -55,6 +55,8 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | 模型 | 模型 ID | 端点 | AI SDK 包 | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 3634b6eca..d02b5c290 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -59,6 +59,8 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | 模型 | Model ID | 端點 | AI SDK Package | | --------------------- | --------------------- | -------------------------------------------------- | --------------------------- | +| GPT 5.5 | gpt-5.5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| GPT 5.5 Pro | gpt-5.5-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | GPT 5.4 Mini | gpt-5.4-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | From 3776d85e15e829fc623427fac122219dd5ea9951 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 24 Apr 2026 14:39:24 -0400 Subject: [PATCH 127/426] zen: gpt-5.5 --- packages/web/src/content/docs/ar/zen.mdx | 2 ++ packages/web/src/content/docs/bs/zen.mdx | 2 ++ packages/web/src/content/docs/da/zen.mdx | 2 ++ packages/web/src/content/docs/de/zen.mdx | 2 ++ packages/web/src/content/docs/es/zen.mdx | 2 ++ packages/web/src/content/docs/fr/zen.mdx | 2 ++ packages/web/src/content/docs/it/zen.mdx | 2 ++ packages/web/src/content/docs/ko/zen.mdx | 2 ++ packages/web/src/content/docs/nb/zen.mdx | 6 ++++-- packages/web/src/content/docs/pl/zen.mdx | 6 ++++-- packages/web/src/content/docs/pt-br/zen.mdx | 4 +++- packages/web/src/content/docs/ru/zen.mdx | 6 ++++-- packages/web/src/content/docs/th/zen.mdx | 4 +++- packages/web/src/content/docs/tr/zen.mdx | 4 +++- packages/web/src/content/docs/zh-cn/zen.mdx | 4 +++- packages/web/src/content/docs/zh-tw/zen.mdx | 4 +++- 16 files changed, 43 insertions(+), 11 deletions(-) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index 42ab5034c..7f439675a 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -148,6 +148,8 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index 6bc25b4c1..4e0c7a77a 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -155,6 +155,8 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 51bae8ef6..0113f8b15 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -155,6 +155,8 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index 30a78ddd6..2aaba57fb 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -144,6 +144,8 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 9534c61de..f775a4633 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -155,6 +155,8 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index cd2a621f5..379185658 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -144,6 +144,8 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 451d40751..19de7dc0a 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -155,6 +155,8 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 1fc661da6..31276d18b 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -144,6 +144,8 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index 6d9834799..08e13d4c8 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -107,8 +107,8 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [modell-id](/docs/config/#models) i OpenCode-konfigurasjonen din -bruker formatet `opencode/`. For eksempel, for GPT 5.4, ville du -brukt `opencode/gpt-5.4` i konfigurasjonen din. +bruker formatet `opencode/`. For eksempel, for GPT 5.5, ville du +brukt `opencode/gpt-5.5` i konfigurasjonen din. --- @@ -155,6 +155,8 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 0e2ee0af7..84d4e2d80 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -107,8 +107,8 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [ID modelu](/docs/config/#models) w Twojej konfiguracji OpenCode używa formatu -`opencode/`. Na przykład dla GPT 5.4 użyjesz w konfiguracji -`opencode/gpt-5.4`. +`opencode/`. Na przykład dla GPT 5.5 użyjesz w konfiguracji +`opencode/gpt-5.5`. --- @@ -155,6 +155,8 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index 73be927b8..d67f06544 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -97,7 +97,7 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -O [model id](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode/`. Por exemplo, para GPT 5.4, você usaria `opencode/gpt-5.4` na sua configuração. +O [model id](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode/`. Por exemplo, para GPT 5.5, você usaria `opencode/gpt-5.5` na sua configuração. --- @@ -144,6 +144,8 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 92b08e238..422f14bdf 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -107,8 +107,8 @@ OpenCode Zen работает как любой другой провайдер | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [идентификатор модели](/docs/config/#models) в вашей конфигурации OpenCode -использует формат `opencode/`. Например, для GPT 5.4 вам нужно -использовать `opencode/gpt-5.4` в своей конфигурации. +использует формат `opencode/`. Например, для GPT 5.5 вам нужно +использовать `opencode/gpt-5.5` в своей конфигурации. --- @@ -155,6 +155,8 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index b082bff00..0ac427c02 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -99,7 +99,7 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -[model id](/docs/config/#models) ใน OpenCode config ของคุณใช้รูปแบบ `opencode/` ตัวอย่างเช่น สำหรับ GPT 5.4 คุณจะใช้ `opencode/gpt-5.4` ใน config ของคุณ +[model id](/docs/config/#models) ใน OpenCode config ของคุณใช้รูปแบบ `opencode/` ตัวอย่างเช่น สำหรับ GPT 5.5 คุณจะใช้ `opencode/gpt-5.5` ใน config ของคุณ --- @@ -146,6 +146,8 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index bf3d1ac3f..eda5cf1b4 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -97,7 +97,7 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -OpenCode yapılandırmanızdaki [model id](/docs/config/#models) `opencode/` biçimini kullanır. Örneğin, GPT 5.4 için yapılandırmanızda `opencode/gpt-5.4` kullanırsınız. +OpenCode yapılandırmanızdaki [model id](/docs/config/#models) `opencode/` biçimini kullanır. Örneğin, GPT 5.5 için yapılandırmanızda `opencode/gpt-5.5` kullanırsınız. --- @@ -144,6 +144,8 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index a74386e8b..57f2d1659 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -97,7 +97,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -在你的 OpenCode 配置中,[模型 ID](/docs/config/#models) 使用 `opencode/` 格式。例如,对于 GPT 5.4,你需要在配置中使用 `opencode/gpt-5.4`。 +在你的 OpenCode 配置中,[模型 ID](/docs/config/#models) 使用 `opencode/` 格式。例如,对于 GPT 5.5,你需要在配置中使用 `opencode/gpt-5.5`。 --- @@ -144,6 +144,8 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index d02b5c290..9c63cf4b2 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -102,7 +102,7 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode 設定中的 [模型 ID](/docs/config/#models) 會使用 `opencode/` -格式。例如,如果是 GPT 5.4,你會在設定中使用 `opencode/gpt-5.4`。 +格式。例如,如果是 GPT 5.5,你會在設定中使用 `opencode/gpt-5.5`。 --- @@ -149,6 +149,8 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | +| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | From 4dab2a8555dabdc40109cc79d7528fca452bbdab Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 24 Apr 2026 14:43:04 -0400 Subject: [PATCH 128/426] zen: gpt-5.5 --- packages/web/src/content/docs/ar/zen.mdx | 2 +- packages/web/src/content/docs/bs/zen.mdx | 2 +- packages/web/src/content/docs/da/zen.mdx | 2 +- packages/web/src/content/docs/de/zen.mdx | 2 +- packages/web/src/content/docs/es/zen.mdx | 2 +- packages/web/src/content/docs/fr/zen.mdx | 2 +- packages/web/src/content/docs/it/zen.mdx | 2 +- packages/web/src/content/docs/ja/zen.mdx | 2 +- packages/web/src/content/docs/ko/zen.mdx | 2 +- packages/web/src/content/docs/nb/zen.mdx | 2 +- packages/web/src/content/docs/pl/zen.mdx | 2 +- packages/web/src/content/docs/pt-br/zen.mdx | 2 +- packages/web/src/content/docs/ru/zen.mdx | 2 +- packages/web/src/content/docs/th/zen.mdx | 2 +- packages/web/src/content/docs/tr/zen.mdx | 2 +- packages/web/src/content/docs/zen.mdx | 2 +- packages/web/src/content/docs/zh-cn/zen.mdx | 2 +- packages/web/src/content/docs/zh-tw/zen.mdx | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index 7f439675a..17fb0020f 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -149,7 +149,7 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index 4e0c7a77a..c57ceb001 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -156,7 +156,7 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 0113f8b15..234ea63ee 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -156,7 +156,7 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index 2aaba57fb..4fc54e12b 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -145,7 +145,7 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index f775a4633..36da8a281 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -156,7 +156,7 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index 379185658..1c5b45a08 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -145,7 +145,7 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 19de7dc0a..70c08152e 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -156,7 +156,7 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index 0d142a73f..f7793544b 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -145,7 +145,7 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 31276d18b..a49413b87 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -145,7 +145,7 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index 08e13d4c8..400256a45 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -156,7 +156,7 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 84d4e2d80..200f69631 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -156,7 +156,7 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index d67f06544..8b69c7ccb 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -145,7 +145,7 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 422f14bdf..d2c24ae9a 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -156,7 +156,7 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index 0ac427c02..64003550c 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -147,7 +147,7 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index eda5cf1b4..1f0a7005b 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -145,7 +145,7 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 97d16618d..d377e4a2a 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -156,7 +156,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index 57f2d1659..edd623df5 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -145,7 +145,7 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 9c63cf4b2..42bff33d9 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -150,7 +150,7 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | | GPT 5.5 | $5.00 | $30.00 | $0.50 | - | -| GPT 5.5 Pro | $60.00 | $360.00 | $60.00 | - | +| GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 | $2.50 | $15.00 | $0.25 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | From 0405bc74e9f0251115232bba62d14d152406675a Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 24 Apr 2026 14:57:23 -0400 Subject: [PATCH 129/426] zen: gpt-5.5 --- packages/web/src/content/docs/ar/zen.mdx | 6 ++++-- packages/web/src/content/docs/bs/zen.mdx | 6 ++++-- packages/web/src/content/docs/da/zen.mdx | 6 ++++-- packages/web/src/content/docs/de/zen.mdx | 6 ++++-- packages/web/src/content/docs/es/zen.mdx | 6 ++++-- packages/web/src/content/docs/fr/zen.mdx | 6 ++++-- packages/web/src/content/docs/it/zen.mdx | 6 ++++-- packages/web/src/content/docs/ja/zen.mdx | 6 ++++-- packages/web/src/content/docs/ko/zen.mdx | 6 ++++-- packages/web/src/content/docs/nb/zen.mdx | 6 ++++-- packages/web/src/content/docs/pl/zen.mdx | 6 ++++-- packages/web/src/content/docs/pt-br/zen.mdx | 6 ++++-- packages/web/src/content/docs/ru/zen.mdx | 6 ++++-- packages/web/src/content/docs/th/zen.mdx | 6 ++++-- packages/web/src/content/docs/tr/zen.mdx | 6 ++++-- packages/web/src/content/docs/zen.mdx | 6 ++++-- packages/web/src/content/docs/zh-cn/zen.mdx | 6 ++++-- packages/web/src/content/docs/zh-tw/zen.mdx | 6 ++++-- 18 files changed, 72 insertions(+), 36 deletions(-) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index 17fb0020f..9f7ed302e 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -148,9 +148,11 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index c57ceb001..d10e4f87f 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -155,9 +155,11 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 234ea63ee..44df1602a 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -155,9 +155,11 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index 4fc54e12b..61a03e7c2 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -144,9 +144,11 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 36da8a281..f0df78118 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -155,9 +155,11 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index 1c5b45a08..09fa4699a 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -144,9 +144,11 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 70c08152e..36c4fd3e8 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -155,9 +155,11 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index f7793544b..504f1fd78 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -144,9 +144,11 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index a49413b87..222f494bc 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -144,9 +144,11 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index 400256a45..c86c282e7 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -155,9 +155,11 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 200f69631..078888965 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -155,9 +155,11 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index 8b69c7ccb..a72a9a90b 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -144,9 +144,11 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index d2c24ae9a..84fedeb10 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -155,9 +155,11 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index 64003550c..efb896601 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -146,9 +146,11 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index 1f0a7005b..c505662cb 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -144,9 +144,11 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index d377e4a2a..6ebf78336 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -155,9 +155,11 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index edd623df5..9248ff174 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -144,9 +144,11 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 42bff33d9..3be6a3da0 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -149,9 +149,11 @@ https://opencode.ai/zen/v1/models | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | -| GPT 5.5 | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (≤ 272K tokens) | $5.00 | $30.00 | $0.50 | - | +| GPT 5.5 (> 272K tokens) | $10.00 | $45.00 | $1.00 | - | | GPT 5.5 Pro | $30.00 | $180.00 | $30.00 | - | -| GPT 5.4 | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (≤ 272K tokens) | $2.50 | $15.00 | $0.25 | - | +| GPT 5.4 (> 272K tokens) | $5.00 | $22.50 | $0.50 | - | | GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - | | GPT 5.4 Mini | $0.75 | $4.50 | $0.075 | - | | GPT 5.4 Nano | $0.20 | $1.25 | $0.02 | - | From 435becbea01183ab3dd5581067c64f885b93c2d8 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 24 Apr 2026 17:11:07 -0400 Subject: [PATCH 130/426] Refactor HttpApi auth middleware wiring (#24168) --- .../server/routes/instance/httpapi/auth.ts | 66 +++++++++++++++ .../server/routes/instance/httpapi/config.ts | 4 +- .../server/routes/instance/httpapi/file.ts | 4 +- .../src/server/routes/instance/httpapi/mcp.ts | 4 +- .../routes/instance/httpapi/permission.ts | 4 +- .../server/routes/instance/httpapi/project.ts | 4 +- .../routes/instance/httpapi/provider.ts | 4 +- .../routes/instance/httpapi/question.ts | 4 +- .../server/routes/instance/httpapi/server.ts | 84 +++---------------- .../routes/instance/httpapi/workspace.ts | 4 +- 10 files changed, 102 insertions(+), 80 deletions(-) create mode 100644 packages/opencode/src/server/routes/instance/httpapi/auth.ts diff --git a/packages/opencode/src/server/routes/instance/httpapi/auth.ts b/packages/opencode/src/server/routes/instance/httpapi/auth.ts new file mode 100644 index 000000000..adddcc4be --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/auth.ts @@ -0,0 +1,66 @@ +import { Effect, Encoding, Layer, Redacted, Schema } from "effect" +import { HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi" +import { Flag } from "@/flag/flag" + +class Unauthorized extends Schema.TaggedErrorClass()( + "Unauthorized", + { message: Schema.String }, + { httpApiStatus: 401 }, +) {} + +export class Authorization extends HttpApiMiddleware.Service()("@opencode/ExperimentalHttpApiAuthorization", { + error: Unauthorized, + security: { + basic: HttpApiSecurity.basic, + authToken: HttpApiSecurity.apiKey({ in: "query", key: "auth_token" }), + }, +}) {} + +const emptyCredential = { + username: "", + password: Redacted.make(""), +} + +function validateCredential( + effect: Effect.Effect, + credential: { readonly username: string; readonly password: typeof emptyCredential.password }, +) { + return Effect.gen(function* () { + if (!Flag.OPENCODE_SERVER_PASSWORD) return yield* effect + + if (credential.username !== (Flag.OPENCODE_SERVER_USERNAME ?? "opencode")) { + return yield* new Unauthorized({ message: "Unauthorized" }) + } + if (Redacted.value(credential.password) !== Flag.OPENCODE_SERVER_PASSWORD) { + return yield* new Unauthorized({ message: "Unauthorized" }) + } + return yield* effect + }) +} + +function decodeCredential(input: string) { + return Encoding.decodeBase64String(input).asEffect().pipe( + Effect.match({ + onFailure: () => emptyCredential, + onSuccess: (header) => { + const parts = header.split(":") + if (parts.length !== 2) return emptyCredential + return { + username: parts[0], + password: Redacted.make(parts[1]), + } + }, + }), + ) +} + +export const authorizationLayer = Layer.succeed( + Authorization, + Authorization.of({ + basic: (effect, { credential }) => validateCredential(effect, credential), + authToken: (effect, { credential }) => + Effect.gen(function* () { + return yield* validateCredential(effect, yield* decodeCredential(Redacted.value(credential))) + }), + }), +) diff --git a/packages/opencode/src/server/routes/instance/httpapi/config.ts b/packages/opencode/src/server/routes/instance/httpapi/config.ts index 2dfdec172..fcdf6d1a3 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/config.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/config.ts @@ -2,6 +2,7 @@ import { Config } from "@/config" import { Provider } from "@/provider" import { Effect, Layer } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" const root = "/config" @@ -33,7 +34,8 @@ export const ConfigApi = HttpApi.make("config") title: "config", description: "Experimental HttpApi config routes.", }), - ), + ) + .middleware(Authorization), ) .annotateMerge( OpenApi.annotations({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/file.ts b/packages/opencode/src/server/routes/instance/httpapi/file.ts index eaf43862a..c55d0c2e7 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/file.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/file.ts @@ -1,6 +1,7 @@ import { File } from "@/file" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" const FileQuery = Schema.Struct({ path: Schema.String, @@ -51,7 +52,8 @@ export const FileApi = HttpApi.make("file") title: "file", description: "Experimental HttpApi file routes.", }), - ), + ) + .middleware(Authorization), ) .annotateMerge( OpenApi.annotations({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts index 91467b1e9..34d4e09e2 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts @@ -1,6 +1,7 @@ import { MCP } from "@/mcp" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" export const McpPaths = { status: "/mcp", @@ -25,7 +26,8 @@ export const McpApi = HttpApi.make("mcp") title: "mcp", description: "Experimental HttpApi MCP routes.", }), - ), + ) + .middleware(Authorization), ) .annotateMerge( OpenApi.annotations({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/permission.ts b/packages/opencode/src/server/routes/instance/httpapi/permission.ts index ed8cb4e27..85dbecd11 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/permission.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/permission.ts @@ -2,6 +2,7 @@ import { Permission } from "@/permission" import { PermissionID } from "@/permission/schema" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" const root = "/permission" @@ -35,7 +36,8 @@ export const PermissionApi = HttpApi.make("permission") title: "permission", description: "Experimental HttpApi permission routes.", }), - ), + ) + .middleware(Authorization), ) .annotateMerge( OpenApi.annotations({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/project.ts b/packages/opencode/src/server/routes/instance/httpapi/project.ts index 7d2d8462f..10cf25118 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/project.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/project.ts @@ -2,6 +2,7 @@ import { Instance } from "@/project/instance" import { Project } from "@/project" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" const root = "/project" @@ -33,7 +34,8 @@ export const ProjectApi = HttpApi.make("project") title: "project", description: "Experimental HttpApi project routes.", }), - ), + ) + .middleware(Authorization), ) .annotateMerge( OpenApi.annotations({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/provider.ts b/packages/opencode/src/server/routes/instance/httpapi/provider.ts index 67831a1fa..dd1a21d2b 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/provider.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/provider.ts @@ -6,6 +6,7 @@ import { ProviderID } from "@/provider/schema" import { mapValues } from "remeda" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" const root = "/provider" @@ -59,7 +60,8 @@ export const ProviderApi = HttpApi.make("provider") title: "provider", description: "Experimental HttpApi provider routes.", }), - ), + ) + .middleware(Authorization), ) .annotateMerge( OpenApi.annotations({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/question.ts b/packages/opencode/src/server/routes/instance/httpapi/question.ts index 3192b530e..526a78ee0 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/question.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/question.ts @@ -2,6 +2,7 @@ import { Question } from "@/question" import { QuestionID } from "@/question/schema" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" const root = "/question" @@ -45,7 +46,8 @@ export const QuestionApi = HttpApi.make("question") title: "question", description: "Question routes.", }), - ), + ) + .middleware(Authorization), ) .annotateMerge( OpenApi.annotations({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index b45ad942b..14c2550ed 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -1,14 +1,14 @@ -import { Effect, Layer, Redacted, Schema } from "effect" -import { HttpApiBuilder, HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi" +import { Effect, Layer, Schema } from "effect" +import { HttpApiBuilder } from "effect/unstable/httpapi" import { HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http" import { AppRuntime } from "@/effect/app-runtime" import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref" import { Observability } from "@/effect" -import { Flag } from "@/flag/flag" import { InstanceBootstrap } from "@/project/bootstrap" import { Instance } from "@/project/instance" import { lazy } from "@/util/lazy" import { Filesystem } from "@/util" +import { authorizationLayer } from "./auth" import { ConfigApi, configHandlers } from "./config" import { FileApi, fileHandlers } from "./file" import { McpApi, mcpHandlers } from "./mcp" @@ -38,56 +38,6 @@ function decode(input: string) { } } -class Unauthorized extends Schema.TaggedErrorClass()( - "Unauthorized", - { message: Schema.String }, - { httpApiStatus: 401 }, -) {} - -class Authorization extends HttpApiMiddleware.Service()("@opencode/ExperimentalHttpApiAuthorization", { - error: Unauthorized, - security: { - basic: HttpApiSecurity.basic, - }, -}) {} - -const normalize = HttpRouter.middleware()( - Effect.gen(function* () { - return (effect) => - Effect.gen(function* () { - const query = yield* HttpServerRequest.schemaSearchParams(Query) - if (!query.auth_token) return yield* effect - const req = yield* HttpServerRequest.HttpServerRequest - const next = req.modify({ - headers: { - ...req.headers, - authorization: `Basic ${query.auth_token}`, - }, - }) - return yield* effect.pipe(Effect.provideService(HttpServerRequest.HttpServerRequest, next)) - }) - }), -).layer - -const auth = Layer.succeed( - Authorization, - Authorization.of({ - basic: (effect, { credential }) => - Effect.gen(function* () { - if (!Flag.OPENCODE_SERVER_PASSWORD) return yield* effect - - const user = Flag.OPENCODE_SERVER_USERNAME ?? "opencode" - if (credential.username !== user) { - return yield* new Unauthorized({ message: "Unauthorized" }) - } - if (Redacted.value(credential.password) !== Flag.OPENCODE_SERVER_PASSWORD) { - return yield* new Unauthorized({ message: "Unauthorized" }) - } - return yield* effect - }), - }), -) - const instance = HttpRouter.middleware()( Effect.gen(function* () { return (effect) => @@ -110,27 +60,17 @@ const instance = HttpRouter.middleware()( }), ).layer -const QuestionSecured = QuestionApi.middleware(Authorization) -const PermissionSecured = PermissionApi.middleware(Authorization) -const ProjectSecured = ProjectApi.middleware(Authorization) -const ProviderSecured = ProviderApi.middleware(Authorization) -const ConfigSecured = ConfigApi.middleware(Authorization) -const WorkspaceSecured = WorkspaceApi.middleware(Authorization) -const FileSecured = FileApi.middleware(Authorization) -const McpSecured = McpApi.middleware(Authorization) - export const routes = Layer.mergeAll( - HttpApiBuilder.layer(ConfigSecured).pipe(Layer.provide(configHandlers)), - HttpApiBuilder.layer(FileSecured).pipe(Layer.provide(fileHandlers)), - HttpApiBuilder.layer(McpSecured).pipe(Layer.provide(mcpHandlers)), - HttpApiBuilder.layer(ProjectSecured).pipe(Layer.provide(projectHandlers)), - HttpApiBuilder.layer(QuestionSecured).pipe(Layer.provide(questionHandlers)), - HttpApiBuilder.layer(PermissionSecured).pipe(Layer.provide(permissionHandlers)), - HttpApiBuilder.layer(ProviderSecured).pipe(Layer.provide(providerHandlers)), - HttpApiBuilder.layer(WorkspaceSecured).pipe(Layer.provide(workspaceHandlers)), + HttpApiBuilder.layer(ConfigApi).pipe(Layer.provide(configHandlers)), + HttpApiBuilder.layer(FileApi).pipe(Layer.provide(fileHandlers)), + HttpApiBuilder.layer(McpApi).pipe(Layer.provide(mcpHandlers)), + HttpApiBuilder.layer(ProjectApi).pipe(Layer.provide(projectHandlers)), + HttpApiBuilder.layer(QuestionApi).pipe(Layer.provide(questionHandlers)), + HttpApiBuilder.layer(PermissionApi).pipe(Layer.provide(permissionHandlers)), + HttpApiBuilder.layer(ProviderApi).pipe(Layer.provide(providerHandlers)), + HttpApiBuilder.layer(WorkspaceApi).pipe(Layer.provide(workspaceHandlers)), ).pipe( - Layer.provide(auth), - Layer.provide(normalize), + Layer.provide(authorizationLayer), Layer.provide(instance), Layer.provide(HttpServer.layerServices), Layer.provideMerge(Observability.layer), diff --git a/packages/opencode/src/server/routes/instance/httpapi/workspace.ts b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts index 596545073..2ab6b03d2 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/workspace.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts @@ -4,6 +4,7 @@ import { WorkspaceAdaptorEntry } from "@/control-plane/types" import * as InstanceState from "@/effect/instance-state" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" const root = "/experimental/workspace" export const WorkspacePaths = { @@ -49,7 +50,8 @@ export const WorkspaceApi = HttpApi.make("workspace") title: "workspace", description: "Experimental HttpApi workspace routes.", }), - ), + ) + .middleware(Authorization), ) .annotateMerge( OpenApi.annotations({ From cf45a8d80752fd4f29a57c6b696c40c3b332c3e9 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 24 Apr 2026 17:11:52 -0400 Subject: [PATCH 131/426] refactor(schema): decode effect schemas directly (#24169) --- packages/opencode/src/acp/agent.ts | 19 ++++++++++--------- packages/opencode/src/cli/cmd/import.ts | 7 +++++-- .../src/control-plane/adaptors/worktree.ts | 11 +++++------ .../src/server/routes/instance/pty.ts | 6 ++++-- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 672b93f6c..24bcb9c2d 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -46,7 +46,7 @@ import { MessageV2 } from "@/session/message-v2" import { Config } from "@/config" import { ConfigMCP } from "@/config/mcp" import { Todo } from "@/session/todo" -import { z } from "zod" +import { Result, Schema } from "effect" import { LoadAPIKeyError } from "ai" import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2" import { applyPatch } from "diff" @@ -54,6 +54,7 @@ import { InstallationVersion } from "@/installation/version" type ModeOption = { id: string; name: string; description?: string } type ModelOption = { modelId: string; name: string } +const decodeTodos = Schema.decodeUnknownResult(Schema.fromJsonString(Schema.Array(Todo.Info))) const DEFAULT_VARIANT_VALUE = "default" @@ -372,14 +373,14 @@ export class Agent implements ACPAgent { } if (part.tool === "todowrite") { - const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output)) - if (parsedTodos.success) { + const parsedTodos = decodeTodos(part.state.output) + if (Result.isSuccess(parsedTodos)) { await this.connection .sessionUpdate({ sessionId, update: { sessionUpdate: "plan", - entries: parsedTodos.data.map((todo) => { + entries: parsedTodos.success.map((todo) => { const status: PlanEntry["status"] = todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"]) return { @@ -394,7 +395,7 @@ export class Agent implements ACPAgent { log.error("failed to send session update for todo", { error }) }) } else { - log.error("failed to parse todo output", { error: parsedTodos.error }) + log.error("failed to parse todo output", { error: parsedTodos.failure }) } } @@ -901,14 +902,14 @@ export class Agent implements ACPAgent { } if (part.tool === "todowrite") { - const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output)) - if (parsedTodos.success) { + const parsedTodos = decodeTodos(part.state.output) + if (Result.isSuccess(parsedTodos)) { await this.connection .sessionUpdate({ sessionId, update: { sessionUpdate: "plan", - entries: parsedTodos.data.map((todo) => { + entries: parsedTodos.success.map((todo) => { const status: PlanEntry["status"] = todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"]) return { @@ -923,7 +924,7 @@ export class Agent implements ACPAgent { log.error("failed to send session update for todo", { error: err }) }) } else { - log.error("failed to parse todo output", { error: parsedTodos.error }) + log.error("failed to parse todo output", { error: parsedTodos.failure }) } } diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index e52120f1a..26256a770 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -13,6 +13,9 @@ import { Filesystem } from "../../util" import { AppRuntime } from "@/effect/app-runtime" import { Schema } from "effect" +const decodeMessageInfo = Schema.decodeUnknownSync(MessageV2.Info) +const decodePart = Schema.decodeUnknownSync(MessageV2.Part) + /** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */ export type ShareData = | { type: "session"; data: SDKSession } @@ -169,7 +172,7 @@ export const ImportCommand = cmd({ ) for (const msg of exportData.messages) { - const msgInfo = MessageV2.Info.zod.parse(msg.info) + const msgInfo = decodeMessageInfo(msg.info) as MessageV2.Info const { id, sessionID: _, ...msgData } = msgInfo Database.use((db) => db @@ -185,7 +188,7 @@ export const ImportCommand = cmd({ ) for (const part of msg.parts) { - const partInfo = MessageV2.Part.zod.parse(part) + const partInfo = decodePart(part) as MessageV2.Part const { id: partId, sessionID: _s, messageID, ...partData } = partInfo Database.use((db) => db diff --git a/packages/opencode/src/control-plane/adaptors/worktree.ts b/packages/opencode/src/control-plane/adaptors/worktree.ts index 8d421b9a3..9c080daa3 100644 --- a/packages/opencode/src/control-plane/adaptors/worktree.ts +++ b/packages/opencode/src/control-plane/adaptors/worktree.ts @@ -2,14 +2,13 @@ import { Schema } from "effect" import { AppRuntime } from "@/effect/app-runtime" import { Worktree } from "@/worktree" import { type WorkspaceAdaptor, WorkspaceInfo } from "../types" -import { zod } from "@/util/effect-zod" -import { withStatics } from "@/util/schema" const WorktreeConfig = Schema.Struct({ name: WorkspaceInfo.fields.name, branch: Schema.String, directory: Schema.String, -}).pipe(withStatics((s) => ({ zod: zod(s) }))) +}) +const decodeWorktreeConfig = Schema.decodeUnknownSync(WorktreeConfig) export const WorktreeAdaptor: WorkspaceAdaptor = { name: "Worktree", @@ -24,7 +23,7 @@ export const WorktreeAdaptor: WorkspaceAdaptor = { } }, async create(info) { - const config = WorktreeConfig.zod.parse(info) + const config = decodeWorktreeConfig(info) await AppRuntime.runPromise( Worktree.Service.use((svc) => svc.createFromInfo({ @@ -36,11 +35,11 @@ export const WorktreeAdaptor: WorkspaceAdaptor = { ) }, async remove(info) { - const config = WorktreeConfig.zod.parse(info) + const config = decodeWorktreeConfig(info) await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove({ directory: config.directory }))) }, target(info) { - const config = WorktreeConfig.zod.parse(info) + const config = decodeWorktreeConfig(info) return { type: "local", directory: config.directory, diff --git a/packages/opencode/src/server/routes/instance/pty.ts b/packages/opencode/src/server/routes/instance/pty.ts index 51c469924..581537221 100644 --- a/packages/opencode/src/server/routes/instance/pty.ts +++ b/packages/opencode/src/server/routes/instance/pty.ts @@ -1,7 +1,7 @@ import { Hono } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" import type { UpgradeWebSocket } from "hono/ws" -import { Effect } from "effect" +import { Effect, Schema } from "effect" import z from "zod" import { AppRuntime } from "@/effect/app-runtime" import { Pty } from "@/pty" @@ -10,6 +10,8 @@ import { NotFoundError } from "@/storage" import { errors } from "../../error" import { jsonRequest, runRequest } from "./trace" +const decodePtyID = Schema.decodeUnknownSync(PtyID) + export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { return new Hono() .get( @@ -171,7 +173,7 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) { onClose: () => void } - const id = PtyID.zod.parse(c.req.param("ptyID")) + const id = decodePtyID(c.req.param("ptyID")) const cursor = (() => { const value = c.req.query("cursor") if (!value) return From 7a02eee0f4002687a5167574744cd00cb06624c4 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 24 Apr 2026 21:13:00 +0000 Subject: [PATCH 132/426] chore: generate --- .../server/routes/instance/httpapi/auth.ts | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/auth.ts b/packages/opencode/src/server/routes/instance/httpapi/auth.ts index adddcc4be..fe72b7822 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/auth.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/auth.ts @@ -8,13 +8,16 @@ class Unauthorized extends Schema.TaggedErrorClass()( { httpApiStatus: 401 }, ) {} -export class Authorization extends HttpApiMiddleware.Service()("@opencode/ExperimentalHttpApiAuthorization", { - error: Unauthorized, - security: { - basic: HttpApiSecurity.basic, - authToken: HttpApiSecurity.apiKey({ in: "query", key: "auth_token" }), +export class Authorization extends HttpApiMiddleware.Service()( + "@opencode/ExperimentalHttpApiAuthorization", + { + error: Unauthorized, + security: { + basic: HttpApiSecurity.basic, + authToken: HttpApiSecurity.apiKey({ in: "query", key: "auth_token" }), + }, }, -}) {} +) {} const emptyCredential = { username: "", @@ -39,19 +42,21 @@ function validateCredential( } function decodeCredential(input: string) { - return Encoding.decodeBase64String(input).asEffect().pipe( - Effect.match({ - onFailure: () => emptyCredential, - onSuccess: (header) => { - const parts = header.split(":") - if (parts.length !== 2) return emptyCredential - return { - username: parts[0], - password: Redacted.make(parts[1]), - } - }, - }), - ) + return Encoding.decodeBase64String(input) + .asEffect() + .pipe( + Effect.match({ + onFailure: () => emptyCredential, + onSuccess: (header) => { + const parts = header.split(":") + if (parts.length !== 2) return emptyCredential + return { + username: parts[0], + password: Redacted.make(parts[1]), + } + }, + }), + ) } export const authorizationLayer = Layer.succeed( From 361d7001d00bdf6da9f3dd89f9d653f70d5056eb Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 24 Apr 2026 17:36:49 -0400 Subject: [PATCH 133/426] Clarify HttpApi migration plan (#24211) --- packages/opencode/specs/effect/http-api.md | 540 +++++---------------- 1 file changed, 126 insertions(+), 414 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index f83b7c055..6d4791af8 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -1,462 +1,174 @@ # HttpApi migration -Practical notes for an eventual migration of `packages/opencode` server routes from the current Hono handlers to Effect `HttpApi`, either as a full replacement or as a parallel surface. +Plan for replacing instance Hono route implementations with Effect `HttpApi` while preserving behavior, OpenAPI, and SDK output during the transition. -## Goal +## End State -Use Effect `HttpApi` where it gives us a better typed contract for: +- JSON route contracts and handlers live in `src/server/routes/instance/httpapi/*`. +- Route modules own their `HttpApiGroup`, schemas, handlers, and route-level middleware. +- `httpapi/server.ts` only composes groups, instance lookup, observability, and the web handler bridge. +- Hono route implementations are deleted once their `HttpApi` replacements are default, tested, and represented in the SDK/OpenAPI pipeline. +- Streaming, SSE, and websocket routes move later through Effect HTTP primitives or another explicit replacement plan; they do not need to fit `HttpApi` if `HttpApi` is the wrong abstraction. -- route definition -- request decoding and validation -- typed success and error responses -- OpenAPI generation -- handler composition inside Effect +## Current State -This should be treated as a later-stage HTTP boundary migration, not a prerequisite for ongoing service, route-handler, or schema work. +- `OPENCODE_EXPERIMENTAL_HTTPAPI` gates the bridge. Default behavior still uses Hono. +- The bridge mounts selected paths in `server/routes/instance/index.ts` before legacy Hono routes. +- Legacy Hono routes remain for default behavior and for `hono-openapi` SDK generation. +- `HttpApi` auth is independent of Hono auth. +- `Authorization` is attached in each route module, not centrally wrapped in `server.ts`. +- Auth supports Basic auth and the legacy `auth_token` query parameter through `HttpApiSecurity.apiKey`. +- Instance context is provided by `httpapi/server.ts` using `directory`, `workspace`, and `x-opencode-directory`. +- `Observability.layer` is provided in the Effect route layer and deduplicated through the shared `memoMap`. -## Core model +## Migration Rules -`HttpApi` is definition-first. +- Preserve runtime behavior first. Semantic changes, new error behavior, or route shape changes need separate PRs. +- Migrate one route group, or one coherent subset of a route group, at a time. +- Reuse existing services. Do not re-architect service logic during HTTP boundary migration. +- Effect Schema owns route DTOs. Keep `.zod` only as compatibility for remaining Hono/OpenAPI surfaces. +- Regenerate the SDK after schema or OpenAPI-affecting changes and verify the diff is expected. +- Do not delete a Hono route until the SDK/OpenAPI pipeline no longer depends on its Hono `describeRoute` entry. -- `HttpApi` is the root API -- `HttpApiGroup` groups related endpoints -- `HttpApiEndpoint` defines a single route and its request / response schemas -- handlers are implemented separately from the contract +## Schema Notes -This is a better fit once route inputs and outputs are already moving toward Effect Schema-first models. +- Use `Schema.Struct(...).annotate({ identifier })` for named OpenAPI refs when handlers return plain objects. +- Use `Schema.Class` only when the handler returns real class instances or the constructor requirement is intentional. +- Keep nested anonymous shapes as `Schema.Struct` unless a named SDK type is useful. +- Avoid parallel hand-written Zod and Effect definitions for the same route boundary. -## Why it is relevant here +## Phases -The current route-effectification work is already pushing handlers toward: +### 1. Stabilize The Bridge -- one `AppRuntime.runPromise(Effect.gen(...))` body -- yielding services from context -- using typed Effect errors instead of Promise wrappers +Before porting more routes, cover the bridge behavior that every route depends on. -That work is a good prerequisite for `HttpApi`. Once the handler body is already a composed Effect, the remaining migration is mostly about replacing the Hono route declaration and validator layer. +- Add tests that hit the Hono-mounted `HttpApi` bridge, not just `HttpApiBuilder.layer` directly. +- Cover auth disabled, Basic auth success, `auth_token` success, missing credentials, and bad credentials. +- Cover `directory` and `x-opencode-directory` instance selection. +- Verify generated SDK output remains unchanged for non-SDK work. +- Fix or remove any implemented-but-unmounted `HttpApi` groups. -## What HttpApi gives us +### 2. Complete The Inventory -### Contracts +Create a route inventory from the actual Hono registrations and classify each route. -Request params, query, payload, success payloads, and typed error payloads are declared in one place using Effect Schema. +Statuses: -### Validation and decoding +- `bridged`: served through the `HttpApi` bridge when the flag is on. +- `implemented`: `HttpApi` group exists but is not mounted through Hono. +- `next`: good JSON candidate for near-term porting. +- `later`: portable, but needs schema/service cleanup first. +- `special`: SSE, websocket, streaming, or UI bridge behavior that likely needs raw Effect HTTP rather than `HttpApi`. -Incoming data is decoded through Effect Schema instead of hand-maintained Zod validators per route. +### 3. Finish JSON Route Parity -### OpenAPI +Port remaining JSON routes in small batches. -`HttpApi` can derive OpenAPI from the API definition, which overlaps with the current `describeRoute(...)` and `resolver(...)` pattern. +Good near-term candidates: -### Typed errors +- top-level reads: `GET /path`, `GET /vcs`, `GET /vcs/diff`, `GET /command`, `GET /agent`, `GET /skill`, `GET /lsp`, `GET /formatter` +- simple mutations: `POST /instance/dispose` +- experimental JSON reads: console, tool, worktree list, resource list +- deferred JSON mutations: `PATCH /config`, project git init, workspace/worktree create/remove/reset, file search, MCP auth flows -`Schema.TaggedErrorClass` maps naturally to endpoint error contracts. +Keep large or stateful groups for later: -## Likely fit for opencode +- `session` +- `sync` +- process-level experimental routes -Best fit first: +### 4. Move OpenAPI And SDK Generation -- JSON request / response endpoints -- route groups that already mostly delegate into services -- endpoints whose request and response models can be defined with Effect Schema +Hono routes cannot be deleted while `hono-openapi` is the source of SDK generation. -Harder / later fit: +Required before route deletion: -- SSE endpoints -- websocket endpoints -- streaming handlers -- routes with heavy Hono-specific middleware assumptions +- Generate the public OpenAPI surface from Effect `HttpApi` for ported routes. +- Keep operation IDs, schemas, status codes, and SDK type names stable unless the change is intentional. +- Compare generated SDK output against `dev` for every route group deletion. +- Remove Hono OpenAPI stubs only after Effect OpenAPI is the SDK source for those paths. -## Current blockers and gaps +### 5. Make HttpApi Default For JSON Routes -### Schema split +After JSON parity and SDK generation are covered: -Many route boundaries still use Zod-first validators. That does not block all experimentation, but full `HttpApi` adoption is easier after the domain and boundary types are more consistently Schema-first with `.zod` compatibility only where needed. +- Flip the bridge default for ported JSON routes. +- Keep a short-lived fallback flag for the old Hono implementation. +- Run the same tests against both the default and fallback path during rollout. +- Stop adding new Hono handlers for JSON routes once the default flips. -### Mixed handler styles +### 6. Delete Hono Route Implementations -Many current `server/routes/instance/*.ts` handlers still mix composed Effect code with smaller Promise- or ALS-backed seams. Migrating those to consistent `Effect.gen(...)` handlers is the low-risk step to do first. +Delete Hono routes group-by-group after each group meets the deletion criteria. -### Non-JSON routes +Deletion criteria: -The server currently includes SSE, websocket, and streaming-style endpoints. Those should not be the first `HttpApi` targets. +- `HttpApi` route is mounted by default. +- Behavior is covered by bridge-level tests. +- OpenAPI/SDK generation comes from Effect for that path. +- SDK diff is zero or explicitly accepted. +- Legacy Hono route is no longer needed as a fallback. -### Existing Hono integration +After deleting a group: -The current server composition, middleware, and docs flow are Hono-centered today. That suggests a parallel or incremental adoption plan is safer than a flag day rewrite. +- Remove its Hono route file or dead endpoints. +- Remove its `.route(...)` registration from `instance/index.ts`. +- Remove duplicate Zod-only route DTOs if Effect Schema now owns the type. +- Regenerate SDK and verify output. -## Recommended strategy +### 7. Replace Special Routes -### 1. Finish the prerequisites first +Special routes need explicit designs before Hono can disappear completely. -- continue route-handler effectification in `server/routes/instance/*.ts` -- continue schema migration toward Effect Schema-first DTOs and errors -- keep removing service facades +- `event`: SSE +- `pty`: websocket +- `tui`: UI/control bridge behavior +- streaming `session` endpoints -### 2. Start with one parallel group +Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Hono implementations, not forcing every transport shape through `HttpApi`. -Introduce one small `HttpApi` group for plain JSON endpoints only. Good initial candidates are the least stateful endpoints in: +## Current Route Status -- `server/routes/instance/question.ts` -- `server/routes/instance/provider.ts` -- `server/routes/instance/permission.ts` +| Area | Status | Notes | +| --- | --- | --- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` partial | reads only; mutation remains Hono | +| `project` | `bridged` partial | reads only; git-init remains Hono | +| `file` | `bridged` partial | list/content/status only | +| `mcp` | `bridged` partial | status only | +| `workspace` | `implemented` | `HttpApi` group exists, but bridge mounting needs verification | +| top-level instance reads | `next` | path, vcs, command, agent, skill, lsp, formatter | +| experimental JSON routes | `next/later` | console, tool, worktree, resource, global session list | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `later` | process/control side effects | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | -Avoid `session.ts`, SSE, websocket, and TUI-facing routes first. +## Next PRs -Recommended first slice: - -- start with `question` -- start with `GET /question` -- start with `POST /question/:requestID/reply` - -Why `question` first: - -- already JSON-only -- already delegates into an Effect service -- proves list + mutation + params + payload + OpenAPI in one small slice -- avoids the harder streaming and middleware cases - -### 3. Reuse existing services - -Do not re-architect business logic during the HTTP migration. `HttpApi` handlers should call the same Effect services already used by the Hono handlers. - -### 4. Bridge into Hono behind a feature flag - -The `HttpApi` routes are bridged into the Hono server via `HttpRouter.toWebHandler` with a shared `memoMap`. This means: - -- one process, one port — no separate server -- the Effect handler shares layer instances with `AppRuntime` (same `Question.Service`, etc.) -- Effect middleware handles auth and instance lookup independently from Hono middleware -- Hono's `.all()` catch-all intercepts matching paths before the Hono route handlers - -The bridge is gated behind `OPENCODE_EXPERIMENTAL_HTTPAPI` (or `OPENCODE_EXPERIMENTAL`). When the flag is off (default), all requests go through the original Hono handlers unchanged. - -```ts -// in instance/index.ts -if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) { - const handler = ExperimentalHttpApiServer.webHandler().handler - app.all("/question", (c) => handler(c.req.raw)).all("/question/*", (c) => handler(c.req.raw)) -} -``` - -The Hono route handlers are always registered (after the bridge) so `hono-openapi` generates the OpenAPI spec entries that feed SDK codegen. When the flag is on, these handlers are dead code — the `.all()` bridge matches first. - -### 5. Observability - -The `webHandler` provides `Observability.layer` via `Layer.provideMerge`. Since the `memoMap` is shared with `AppRuntime`, the tracing provider is deduplicated — no extra initialization cost. - -This gives: - -- **spans**: `Effect.fn("QuestionHttpApi.list")` etc. appear in traces alongside service-layer spans -- **HTTP logs**: `HttpMiddleware.logger` emits structured `Effect.log` entries with `http.method`, `http.url`, `http.status` annotations, flowing to motel via `OtlpLogger` - -### 6. Migrate JSON route groups gradually - -As each route group is ported to `HttpApi`: - -1. add `.get(...)` / `.post(...)` bridge entries to the flag block in `server/routes/instance/index.ts` -2. for partial ports (e.g. only `GET /provider/auth`), bridge only the specific path -3. keep the legacy Hono route registered behind it for OpenAPI / SDK generation until the spec pipeline changes -4. verify SDK output is unchanged - -Leave streaming-style endpoints on Hono until there is a clear reason to move them. - -## Schema rule for HttpApi work - -Every `HttpApi` slice should follow `specs/effect/schema.md` and the Schema -> Zod interop rule in `specs/effect/migration.md`. - -Default rule: - -- Effect Schema owns the type -- `.zod` exists only as a compatibility surface -- do not introduce a new hand-written Zod schema for a type that is already migrating to Effect Schema - -Practical implication for `HttpApi` migration: - -- if a route boundary already depends on a shared DTO, ID, input, output, or tagged error, migrate that model to Effect Schema first or in the same change -- if an existing Hono route or tool still needs Zod, derive it with `@/util/effect-zod` -- avoid maintaining parallel Zod and Effect definitions for the same request or response type - -Ordering for a route-group migration: - -1. move implicated shared `schema.ts` leaf types to Effect Schema first -2. move exported `Info` / `Input` / `Output` route DTOs to Effect Schema -3. move tagged route-facing errors to `Schema.TaggedErrorClass` where needed -4. switch existing Zod boundary validators to derived `.zod` -5. define the `HttpApi` contract from the canonical Effect schemas -6. regenerate the SDK (`./packages/sdk/js/script/build.ts`) and verify zero diff against `dev` - -SDK shape rule: - -- every schema migration must preserve the generated SDK output byte-for-byte **unless the new ref is intentional** (see Schema.Class vs Schema.Struct below) -- if an unintended diff appears in `packages/sdk/js/src/v2/gen/types.gen.ts`, the migration introduced an unintended API surface change — fix it before merging - -### Schema.Class vs Schema.Struct - -The pattern choice determines whether a schema becomes a **named** export in the SDK or stays **anonymous inline**. - -**Schema.Class** emits a named `$ref` in OpenAPI via its identifier → produces a named `export type Foo = ...` in `types.gen.ts`: - -```ts -export class Info extends Schema.Class("FooConfig")({ ... }) { - static readonly zod = zod(this) -} -``` - -**Schema.Struct** stays anonymous and is inlined everywhere it is referenced: - -```ts -export const Info = Schema.Struct({ ... }).pipe( - withStatics((s) => ({ zod: zod(s) })), -) -export type Info = Schema.Schema.Type -``` - -When to use each: - -- Use **Schema.Class** when: - - the original Zod had `.meta({ ref: ... })` (preserve the existing named SDK type byte-for-byte) - - the schema is a top-level endpoint request or response (SDK consumers benefit from a stable importable name) -- Use **Schema.Struct** when: - - the type is only used as a nested field inside another named schema - - the original Zod was anonymous and promoting it would bloat SDK types with no import value - -Promoting a previously-anonymous schema to Schema.Class is acceptable when it is top-level or endpoint-facing, but call it out in the PR — it is an additive SDK change (`export type Foo = ...` newly appears) even if it preserves the JSON shape. - -Schemas that are **not** pure objects (enums, unions, records, tuples) cannot use Schema.Class. For those — and for pure-object schemas where handlers populate plain objects rather than class instances — add `.annotate({ identifier: "FooName" })` to get the same named-ref behavior without the `instanceof` requirement: - -```ts -export const Action = Schema.Literals(["ask", "allow", "deny"]).annotate({ identifier: "PermissionActionConfig" }) -``` - -Temporary exception: - -- it is acceptable to keep a route-local Zod schema for the first spike only when the type is boundary-local and migrating it would create unrelated churn -- if that happens, leave a short note so the type does not become a permanent second source of truth - -## First vertical slice - -The first `HttpApi` spike should be intentionally small and repeatable. - -Chosen slice: - -- group: `question` -- endpoints: `GET /question` and `POST /question/:requestID/reply` - -Non-goals: - -- no `session` routes -- no SSE or websocket routes -- no auth redesign -- no broad service refactor - -Behavior rule: - -- preserve current runtime behavior first -- treat semantic changes such as introducing new `404` behavior as a separate follow-up unless they are required to make the contract honest - -Add `POST /question/:requestID/reject` only after the first two endpoints work cleanly. - -## Repeatable slice template - -Use the same sequence for each route group. - -1. Pick one JSON-only route group that already mostly delegates into services. -2. Identify the shared DTOs, IDs, and errors implicated by that slice. -3. Apply the schema migration ordering above so those types are Effect Schema-first. -4. Define the `HttpApi` contract separately from the handlers. -5. Implement handlers by yielding the existing service from context. -6. Mount the new surface in parallel behind the `OPENCODE_EXPERIMENTAL_HTTPAPI` bridge. -7. Regenerate the SDK and verify zero diff against `dev` (see SDK shape rule above). -8. Add one end-to-end test and one OpenAPI-focused test. -9. Compare ergonomics before migrating the next endpoint. - -Rule of thumb: - -- migrate one route group at a time -- migrate one or two endpoints first, not the whole file -- keep business logic in the existing service -- keep the first spike easy to delete if the experiment is not worth continuing - -## Example structure - -Placement rule: - -- keep `HttpApi` code under `src/server`, not `src/effect` -- `src/effect` should stay focused on runtimes, layers, instance state, and shared Effect plumbing -- place each `HttpApi` slice next to the HTTP boundary it serves -- for instance-scoped routes, prefer `src/server/routes/instance/httpapi/*` -- if control-plane routes ever migrate, prefer `src/server/routes/control/httpapi/*` - -Suggested file layout for a repeatable spike: - -- `src/server/routes/instance/httpapi/question.ts` — contract and handler layer for one route group -- `src/server/routes/instance/httpapi/server.ts` — bridged Effect HTTP layer that composes all groups -- route or OpenAPI verification should live alongside the existing server tests; there is no dedicated `question-httpapi` test file on this branch - -Suggested responsibilities: - -- `question.ts` defines the `HttpApi` contract and `HttpApiBuilder.group(...)` handlers -- `server.ts` composes all route groups into one `HttpRouter.toWebHandler(...)` bridge with shared middleware (auth, instance lookup) -- tests should verify the bridged routes through the normal server surface - -## Example migration shape - -Each route-group spike should follow the same shape. - -### 1. Contract - -- define an experimental `HttpApi` -- define one `HttpApiGroup` -- define endpoint params, payload, success, and error schemas from canonical Effect schemas -- annotate summary, description, and operation ids explicitly so generated docs are stable - -### 2. Handler layer - -- implement with `HttpApiBuilder.group(api, groupName, ...)` -- yield the existing Effect service from context -- keep handler bodies thin -- keep transport mapping at the HTTP boundary only - -### 3. Bridged server - -- the Effect HTTP layer is composed in `httpapi/server.ts` -- it is mounted into the Hono app via `HttpRouter.toWebHandler(...)` -- routes keep their normal instance paths and are gated by the `OPENCODE_EXPERIMENTAL_HTTPAPI` flag -- the legacy Hono handlers stay registered after the bridge so current OpenAPI / SDK generation still works - -### 4. Verification - -- seed real state through the existing service -- call the bridged endpoints with the flag enabled -- assert that the service behavior is unchanged -- assert that the generated OpenAPI contains the migrated paths and schemas - -## Boundary composition - -The Effect `HttpApi` layer owns its own auth and instance middleware, but it is currently mounted inside the existing Hono server. - -### Auth - -- the bridged `HttpApi` layer implements auth as an `HttpApiMiddleware.Service` using `HttpApiSecurity.basic` -- each route group's `HttpApi` is wrapped with `.middleware(Authorization)` before being served -- this is independent of the Hono auth layer; the current bridge keeps the responsibility local to the `HttpApi` slice - -### Instance and workspace lookup - -- the bridged `HttpApi` layer resolves instance context via an `HttpRouter.middleware` that reads `x-opencode-directory` headers and `directory` query params -- this is the Effect equivalent of the Hono `WorkspaceRouterMiddleware` -- `HttpApi` handlers yield services from context and assume the correct instance has already been provided - -### Error mapping - -- keep domain and service errors typed in the service layer -- declare typed transport errors on the endpoint only when the route can actually return them intentionally -- request decoding failures are transport-level `400`s handled by Effect `HttpApi` automatically -- storage or lookup failures that are part of the route contract should be declared as typed endpoint errors - -## Exit criteria for the spike - -The first slice is successful if: - -- the bridged endpoints serve correctly through the existing Hono host when the flag is enabled -- the handlers reuse the existing Effect service -- request decoding and response shapes are schema-defined from canonical Effect schemas -- any remaining Zod boundary usage is derived from `.zod` or clearly temporary -- OpenAPI is generated from the `HttpApi` contract -- the tests are straightforward enough that the next slice feels mechanical - -## Learnings - -### Schema - -- `Schema.Class` works well for route DTOs such as `Question.Request`, `Question.Info`, and `Question.Reply`. -- scalar or collection schemas such as `Question.Answer` should stay as schemas and use helpers like `withStatics(...)` instead of being forced into classes. -- if an `HttpApi` success schema uses `Schema.Class`, the handler or underlying service needs to return real schema instances rather than plain objects. `Schema.Class`'s Declaration AST enforces `input instanceof self || input.[ClassTypeId]` during encode (see effect-smol `Schema.ts:10479-10484`). Plain objects from zod parse fail with `Expected Foo, got {...}`. This surfaced on `GET /config` where the service returns zod-parsed plain objects and `Config.InfoSchema` referenced `ConfigProvider.Info` (class). The fix was to convert pure-object classes to `Schema.Struct(...).annotate({ identifier: "..." })` — same named SDK `$ref`, no instance requirement. Verified byte-identical `types.gen.ts` vs `dev`. -- internal event payloads can stay anonymous when we want to avoid adding extra named OpenAPI component churn for non-route shapes. -- `Schema.Class` emits named `$ref` in OpenAPI — only use it for types that already had `.meta({ ref })` in the old Zod schema **and** when the handler/service returns real instances. For schemas that need a named `$ref` but are populated from plain objects, use `Schema.Struct(...).annotate({ identifier: "..." })` instead. Inner/nested types should stay as `Schema.Struct` to avoid SDK shape changes. - -### Integration - -- `HttpRouter.toWebHandler` with the shared `memoMap` from `run-service.ts` cleanly bridges Effect routes into Hono — one process, one port, shared layer instances. -- `Observability.layer` must be explicitly provided via `Layer.provideMerge` in the routes layer for OTEL spans and HTTP logs to flow. The `memoMap` deduplicates it with `AppRuntime` — no extra cost. -- `HttpMiddleware.logger` (enabled by default when `disableLogger` is not set) emits structured `Effect.log` entries with `http.method`, `http.url`, `http.status` — these flow through `OtlpLogger` to motel. -- Hono OpenAPI stubs must remain registered for SDK codegen until the SDK pipeline reads from the Effect OpenAPI spec instead. -- the `OPENCODE_EXPERIMENTAL_HTTPAPI` flag gates the bridge at the Hono router level — default off, no behavior change unless opted in. - -## Route inventory - -Status legend: - -- `bridged` - Effect HttpApi slice exists and is bridged into Hono behind the flag -- `done` - Effect HttpApi slice exists but not yet bridged -- `next` - good near-term candidate -- `later` - possible, but not first wave -- `defer` - not a good early `HttpApi` target - -Current instance route inventory: - -- `question` - `bridged` - endpoints: `GET /question`, `POST /question/:requestID/reply`, `POST /question/:requestID/reject` -- `permission` - `bridged` - endpoints: `GET /permission`, `POST /permission/:requestID/reply` -- `provider` - `bridged` - endpoints: `GET /provider`, `GET /provider/auth`, `POST /provider/:providerID/oauth/authorize`, `POST /provider/:providerID/oauth/callback` -- `config` - `bridged` (partial) - bridged endpoints: `GET /config`, `GET /config/providers` - defer `PATCH /config` for now -- `project` - `bridged` (partial) - bridged endpoints: `GET /project`, `GET /project/current` - defer git-init mutation first -- `workspace` - `bridged` - best small reads: `GET /experimental/workspace/adaptor`, `GET /experimental/workspace`, `GET /experimental/workspace/status` - defer create/remove mutations first -- `file` - `bridged` (partial) - bridged endpoints: `GET /file`, `GET /file/content`, `GET /file/status` - defer search endpoints first -- `mcp` - `bridged` (partial) - bridged endpoints: `GET /mcp` - defer interactive OAuth/auth flows first -- `session` - `defer` - large, stateful, mixes CRUD with prompt/shell/command/share/revert flows and a streaming route -- `event` - `defer` - SSE only -- `global` - `defer` - mixed bag with SSE and process-level side effects -- `pty` - `defer` - websocket-heavy route surface -- `tui` - `defer` - queue-style UI bridge, weak early `HttpApi` fit - -Recommended near-term sequence: - -1. `workspace` read endpoints (`GET /experimental/workspace/adaptor`, `GET /experimental/workspace`, `GET /experimental/workspace/status`) -2. `file` JSON read endpoints -3. `mcp` JSON read endpoints +1. Add bridge-level auth and instance-context tests for the current `HttpApi` bridge. +2. Produce a generated route inventory from Hono registrations and update `Current Route Status` with exact paths. +3. Fix the `workspace` status: mount it if it should be reachable, or remove it from the composed `HttpApi` layer. +4. Port the top-level JSON reads. +5. Start the Effect OpenAPI/SDK generation path for already-bridged routes. ## Checklist -- [x] add one small spike that defines an `HttpApi` group for a simple JSON route set -- [x] use Effect Schema request / response types for that slice -- [x] keep the underlying service calls identical to the current handlers -- [x] compare generated OpenAPI against the current Hono/OpenAPI setup -- [x] document how auth, instance lookup, and error mapping would compose in the new stack -- [x] bridge Effect routes into Hono via `toWebHandler` with shared `memoMap` -- [x] gate behind `OPENCODE_EXPERIMENTAL_HTTPAPI` flag -- [x] verify OTEL spans and HTTP logs flow to motel -- [x] bridge question, permission, and provider auth routes -- [x] port remaining provider endpoints (`GET /provider`, OAuth mutations) -- [x] port `config` providers read endpoint -- [x] port `project` read endpoints (`GET /project`, `GET /project/current`) -- [x] port `GET /config` full read endpoint -- [x] port `workspace` read endpoints -- [x] port `file` JSON read endpoints -- [x] port `mcp` status read endpoint -- [ ] decide when to remove the flag and make Effect routes the default - -## Rule of thumb - -Do not start with the hardest route file. - -If `HttpApi` is adopted here, it should arrive after the handler body is already Effect-native and after the relevant request / response models have moved to Effect Schema. +- [x] Add first `HttpApi` JSON route slices. +- [x] Bridge selected `HttpApi` routes into Hono behind `OPENCODE_EXPERIMENTAL_HTTPAPI`. +- [x] Reuse existing Effect services in handlers. +- [x] Provide auth, instance lookup, and observability in the Effect route layer. +- [x] Attach auth middleware in route modules. +- [x] Support `auth_token` as a query security scheme. +- [ ] Add bridge-level auth and instance tests. +- [ ] Complete exact Hono route inventory. +- [ ] Resolve implemented-but-unmounted route groups. +- [ ] Port remaining JSON routes. +- [ ] Generate SDK/OpenAPI from Effect routes. +- [ ] Flip ported JSON routes to default-on with fallback. +- [ ] Delete replaced Hono route implementations. +- [ ] Replace SSE/websocket/streaming Hono routes with non-Hono implementations. From 872cdff2ab1997f071bc29c17910d3a995bb2540 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Fri, 24 Apr 2026 17:37:28 -0400 Subject: [PATCH 134/426] ignore: denounce ai spammer --- .github/VOUCHED.td | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index 65bda9804..589002d08 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -33,3 +33,4 @@ simonklee -spider-yamet clawdbot/llm psychosis, spam pinging the team thdxr -toastythebot +-davidbernat looks to be a clawdbot that spams team and sends super weird emails, doesnt appear to be a real person From c4e33d31680f4b0804045e5b9f0ed78596d33ea4 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 24 Apr 2026 21:38:31 +0000 Subject: [PATCH 135/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 6d4791af8..ad9fcb2ba 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -130,23 +130,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| --- | --- | --- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` partial | reads only; mutation remains Hono | -| `project` | `bridged` partial | reads only; git-init remains Hono | -| `file` | `bridged` partial | list/content/status only | -| `mcp` | `bridged` partial | status only | -| `workspace` | `implemented` | `HttpApi` group exists, but bridge mounting needs verification | -| top-level instance reads | `next` | path, vcs, command, agent, skill, lsp, formatter | -| experimental JSON routes | `next/later` | console, tool, worktree, resource, global session list | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `later` | process/control side effects | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| Area | Status | Notes | +| ------------------------ | ----------------- | -------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` partial | reads only; mutation remains Hono | +| `project` | `bridged` partial | reads only; git-init remains Hono | +| `file` | `bridged` partial | list/content/status only | +| `mcp` | `bridged` partial | status only | +| `workspace` | `implemented` | `HttpApi` group exists, but bridge mounting needs verification | +| top-level instance reads | `next` | path, vcs, command, agent, skill, lsp, formatter | +| experimental JSON routes | `next/later` | console, tool, worktree, resource, global session list | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `later` | process/control side effects | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Next PRs From 4a67905266633f1867aaf89d863b4a8d149d5237 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Fri, 24 Apr 2026 17:39:06 -0400 Subject: [PATCH 136/426] fix: ensure gpt-5.5 compacts at correct context size when using openai oauth (#24212) --- packages/opencode/src/plugin/codex.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index e05111fc6..60d2d5b47 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -390,6 +390,16 @@ export async function CodexAuthPlugin(input: PluginInput): Promise { output: 0, cache: { read: 0, write: 0 }, } + + // gpt-5.5 models temporarily have restricted context window size for codex plans + if (model.id.includes("gpt-5.5")) { + model.limit = { + context: 400_000, + //@ts-expect-error incorrect type for v1 sdk but works + input: 272_000, + output: 128_000, + } + } } return { From bb3509b5ff445912e2b6d5f594866e92b42eb463 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 24 Apr 2026 17:42:10 -0400 Subject: [PATCH 137/426] fix(opencode): clarify git amend condition to require verifying commit landed (#19937) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: opencode-agent[bot] Co-authored-by: Shoubhit Dash --- packages/opencode/src/tool/bash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/tool/bash.txt b/packages/opencode/src/tool/bash.txt index 668cea307..c2fe87379 100644 --- a/packages/opencode/src/tool/bash.txt +++ b/packages/opencode/src/tool/bash.txt @@ -58,7 +58,7 @@ Git Safety Protocol: - NEVER skip hooks (--no-verify, --no-gpg-sign, etc) unless the user explicitly requests it - NEVER run force push to main/master, warn the user if they request it - Avoid git commit --amend. ONLY use --amend when ALL conditions are met: - (1) User explicitly requested amend, OR commit SUCCEEDED but pre-commit hook auto-modified files that need including + (1) User explicitly requested amend, OR the commit succeeded and pre-commit hooks auto-modified files that need including — verify by checking `git log` that HEAD is the new commit before amending (2) HEAD commit was created by you in this conversation (verify: git log -1 --format='%an %ae') (3) Commit has NOT been pushed to remote (verify: git status shows "Your branch is ahead") - CRITICAL: If commit FAILED or was REJECTED by hook, NEVER amend - fix the issue and create a NEW commit From 5a04de231ee455bb112afc7b19af5c8e4144c6a2 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 24 Apr 2026 17:42:52 -0400 Subject: [PATCH 138/426] refactor(ripgrep): migrate result schemas to effect (#24213) --- packages/opencode/src/file/ripgrep.ts | 127 +++++++++--------- .../src/server/routes/instance/file.ts | 2 +- 2 files changed, 63 insertions(+), 66 deletions(-) diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index d6fd61f1d..64a2e3d8e 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -1,7 +1,6 @@ import path from "path" -import z from "zod" import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { Cause, Context, Effect, Fiber, Layer, Queue, Stream } from "effect" +import { Cause, Context, Effect, Fiber, Layer, Queue, Schema, Stream } from "effect" import type { PlatformError } from "effect/PlatformError" import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http" import { ChildProcess } from "effect/unstable/process" @@ -12,6 +11,8 @@ import { Global } from "@/global" import { Log } from "@/util" import { sanitizedProcessEnv } from "@/util/opencode-process" import { which } from "@/util/which" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" const log = Log.create({ service: "ripgrep" }) const VERSION = "15.1.0" @@ -25,83 +26,82 @@ const PLATFORM = { "x64-win32": { platform: "x86_64-pc-windows-msvc", extension: "zip" }, } as const -const Stats = z.object({ - elapsed: z.object({ - secs: z.number(), - nanos: z.number(), - human: z.string(), - }), - searches: z.number(), - searches_with_match: z.number(), - bytes_searched: z.number(), - bytes_printed: z.number(), - matched_lines: z.number(), - matches: z.number(), +const TimeStats = Schema.Struct({ + secs: Schema.Number, + nanos: Schema.Number, + human: Schema.String, }) -const Begin = z.object({ - type: z.literal("begin"), - data: z.object({ - path: z.object({ - text: z.string(), - }), +const Stats = Schema.Struct({ + elapsed: TimeStats, + searches: Schema.Number, + searches_with_match: Schema.Number, + bytes_searched: Schema.Number, + bytes_printed: Schema.Number, + matched_lines: Schema.Number, + matches: Schema.Number, +}) + +const PathText = Schema.Struct({ + text: Schema.String, +}) + +const Begin = Schema.Struct({ + type: Schema.Literal("begin"), + data: Schema.Struct({ + path: PathText, }), }) -export const Match = z.object({ - type: z.literal("match"), - data: z.object({ - path: z.object({ - text: z.string(), - }), - lines: z.object({ - text: z.string(), - }), - line_number: z.number(), - absolute_offset: z.number(), - submatches: z.array( - z.object({ - match: z.object({ - text: z.string(), - }), - start: z.number(), - end: z.number(), +export const SearchMatch = Schema.Struct({ + path: PathText, + lines: Schema.Struct({ + text: Schema.String, + }), + line_number: Schema.Number, + absolute_offset: Schema.Number, + submatches: Schema.Array( + Schema.Struct({ + match: Schema.Struct({ + text: Schema.String, }), - ), - }), + start: Schema.Number, + end: Schema.Number, + }), + ), +}).pipe(withStatics((s) => ({ zod: zod(s) }))) + +export const Match = Schema.Struct({ + type: Schema.Literal("match"), + data: SearchMatch, }) -const End = z.object({ - type: z.literal("end"), - data: z.object({ - path: z.object({ - text: z.string(), - }), - binary_offset: z.number().nullable(), +const End = Schema.Struct({ + type: Schema.Literal("end"), + data: Schema.Struct({ + path: PathText, + binary_offset: Schema.NullOr(Schema.Number), stats: Stats, }), }) -const Summary = z.object({ - type: z.literal("summary"), - data: z.object({ - elapsed_total: z.object({ - human: z.string(), - nanos: z.number(), - secs: z.number(), - }), +const Summary = Schema.Struct({ + type: Schema.Literal("summary"), + data: Schema.Struct({ + elapsed_total: TimeStats, stats: Stats, }), }) -const Result = z.union([Begin, Match, End, Summary]) +const Result = Schema.Union([Begin, Match, End, Summary]) +const decodeResult = Schema.decodeUnknownEffect(Schema.fromJsonString(Result)) -export type Result = z.infer -export type Match = z.infer +export type Result = Schema.Schema.Type +export type Match = Schema.Schema.Type export type Item = Match["data"] -export type Begin = z.infer -export type End = z.infer -export type Summary = z.infer +export type Begin = Schema.Schema.Type +export type End = Schema.Schema.Type +export type Summary = Schema.Schema.Type export type Row = Match["data"] export interface SearchResult { @@ -187,10 +187,7 @@ function row(data: Row): Row { } function parse(line: string) { - return Effect.try({ - try: () => Result.parse(JSON.parse(line)), - catch: (cause) => new Error("invalid ripgrep output", { cause }), - }) + return decodeResult(line).pipe(Effect.mapError((cause) => new Error("invalid ripgrep output", { cause }))) } function fail(queue: Queue.Queue, err: PlatformError | Error) { diff --git a/packages/opencode/src/server/routes/instance/file.ts b/packages/opencode/src/server/routes/instance/file.ts index 661322127..65b3cbad3 100644 --- a/packages/opencode/src/server/routes/instance/file.ts +++ b/packages/opencode/src/server/routes/instance/file.ts @@ -21,7 +21,7 @@ export const FileRoutes = lazy(() => description: "Matches", content: { "application/json": { - schema: resolver(Ripgrep.Match.shape.data.array()), + schema: resolver(Ripgrep.SearchMatch.zod.array()), }, }, }, From 97eb9fdee839020e18a1cf80bf4c5873e8138633 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 24 Apr 2026 18:03:51 -0400 Subject: [PATCH 139/426] test(httpapi): cover hono bridge middleware (#24216) --- .../server/routes/instance/httpapi/project.ts | 4 +- .../test/server/httpapi-bridge.test.ts | 131 ++++++++++++++++++ 2 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 packages/opencode/test/server/httpapi-bridge.test.ts diff --git a/packages/opencode/src/server/routes/instance/httpapi/project.ts b/packages/opencode/src/server/routes/instance/httpapi/project.ts index 10cf25118..6d3143df8 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/project.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/project.ts @@ -1,4 +1,4 @@ -import { Instance } from "@/project/instance" +import * as InstanceState from "@/effect/instance-state" import { Project } from "@/project" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" @@ -54,7 +54,7 @@ export const projectHandlers = Layer.unwrap( }) const current = Effect.fn("ProjectHttpApi.current")(function* () { - return Instance.project + return (yield* InstanceState.context).project }) return HttpApiBuilder.group(ProjectApi, "project", (handlers) => diff --git a/packages/opencode/test/server/httpapi-bridge.test.ts b/packages/opencode/test/server/httpapi-bridge.test.ts new file mode 100644 index 000000000..29f85b881 --- /dev/null +++ b/packages/opencode/test/server/httpapi-bridge.test.ts @@ -0,0 +1,131 @@ +import { afterEach, describe, expect, test } from "bun:test" +import type { UpgradeWebSocket } from "hono/ws" +import { Flag } from "../../src/flag/flag" +import { Instance } from "../../src/project/instance" +import { InstanceRoutes } from "../../src/server/routes/instance" +import { FilePaths } from "../../src/server/routes/instance/httpapi/file" +import { Log } from "../../src/util" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" + +void Log.init({ print: false }) + +const original = { + OPENCODE_EXPERIMENTAL_HTTPAPI: Flag.OPENCODE_EXPERIMENTAL_HTTPAPI, + OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD, + OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, +} + +const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket + +function app(input?: { password?: string; username?: string }) { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true + Flag.OPENCODE_SERVER_PASSWORD = input?.password + Flag.OPENCODE_SERVER_USERNAME = input?.username + return InstanceRoutes(websocket) +} + +function authorization(username: string, password: string) { + return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}` +} + +function fileUrl(input?: { directory?: string; token?: string }) { + const url = new URL(`http://localhost${FilePaths.content}`) + url.searchParams.set("path", "hello.txt") + if (input?.directory) url.searchParams.set("directory", input.directory) + if (input?.token) url.searchParams.set("auth_token", input.token) + return url +} + +afterEach(async () => { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original.OPENCODE_EXPERIMENTAL_HTTPAPI + Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD + Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME + await Instance.disposeAll() + await resetDatabase() +}) + +describe("HttpApi Hono bridge", () => { + test("allows requests when auth is disabled", async () => { + await using tmp = await tmpdir({ git: true }) + await Bun.write(`${tmp.path}/hello.txt`, "hello") + + const response = await app().request(fileUrl(), { + headers: { + "x-opencode-directory": tmp.path, + }, + }) + + expect(response.status).toBe(200) + expect(await response.json()).toMatchObject({ content: "hello" }) + }) + + test("provides instance context to bridged handlers", async () => { + await using tmp = await tmpdir({ git: true }) + + const response = await app().request("/project/current", { + headers: { + "x-opencode-directory": tmp.path, + }, + }) + + expect(response.status).toBe(200) + expect(await response.json()).toMatchObject({ worktree: tmp.path }) + }) + + test("requires credentials when auth is enabled", async () => { + await using tmp = await tmpdir({ git: true }) + await Bun.write(`${tmp.path}/hello.txt`, "hello") + + const [missing, bad, good] = await Promise.all([ + app({ password: "secret" }).request(fileUrl(), { + headers: { "x-opencode-directory": tmp.path }, + }), + app({ password: "secret" }).request(fileUrl(), { + headers: { + authorization: authorization("opencode", "wrong"), + "x-opencode-directory": tmp.path, + }, + }), + app({ password: "secret" }).request(fileUrl(), { + headers: { + authorization: authorization("opencode", "secret"), + "x-opencode-directory": tmp.path, + }, + }), + ]) + + expect(missing.status).toBe(401) + expect(bad.status).toBe(401) + expect(good.status).toBe(200) + }) + + test("accepts auth_token query credentials", async () => { + await using tmp = await tmpdir({ git: true }) + await Bun.write(`${tmp.path}/hello.txt`, "hello") + + const response = await app({ password: "secret" }).request(fileUrl({ token: Buffer.from("opencode:secret").toString("base64") }), { + headers: { + "x-opencode-directory": tmp.path, + }, + }) + + expect(response.status).toBe(200) + }) + + test("selects instance from query before directory header", async () => { + await using header = await tmpdir({ git: true }) + await using query = await tmpdir({ git: true }) + await Bun.write(`${header.path}/hello.txt`, "header") + await Bun.write(`${query.path}/hello.txt`, "query") + + const response = await app().request(fileUrl({ directory: query.path }), { + headers: { + "x-opencode-directory": header.path, + }, + }) + + expect(response.status).toBe(200) + expect(await response.json()).toMatchObject({ content: "query" }) + }) +}) From 5cd178ba7008969c9fc711c78603d7e3144b4ce8 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Fri, 24 Apr 2026 22:05:23 +0000 Subject: [PATCH 140/426] chore: generate --- packages/opencode/test/server/httpapi-bridge.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/opencode/test/server/httpapi-bridge.test.ts b/packages/opencode/test/server/httpapi-bridge.test.ts index 29f85b881..3f0de26a1 100644 --- a/packages/opencode/test/server/httpapi-bridge.test.ts +++ b/packages/opencode/test/server/httpapi-bridge.test.ts @@ -104,11 +104,14 @@ describe("HttpApi Hono bridge", () => { await using tmp = await tmpdir({ git: true }) await Bun.write(`${tmp.path}/hello.txt`, "hello") - const response = await app({ password: "secret" }).request(fileUrl({ token: Buffer.from("opencode:secret").toString("base64") }), { - headers: { - "x-opencode-directory": tmp.path, + const response = await app({ password: "secret" }).request( + fileUrl({ token: Buffer.from("opencode:secret").toString("base64") }), + { + headers: { + "x-opencode-directory": tmp.path, + }, }, - }) + ) expect(response.status).toBe(200) }) From 1e4b7b5451dc924515444c006f6babbb9f24bc85 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Sat, 25 Apr 2026 10:25:57 +1000 Subject: [PATCH 141/426] Add Roslyn support for Razor and C# scripts (#24228) --- packages/opencode/src/lsp/language.ts | 1 + packages/opencode/src/lsp/server.ts | 156 ++++++++++++++++++++++---- packages/web/src/content/docs/lsp.mdx | 3 +- 3 files changed, 135 insertions(+), 25 deletions(-) diff --git a/packages/opencode/src/lsp/language.ts b/packages/opencode/src/lsp/language.ts index 58f4c8488..07a2e9723 100644 --- a/packages/opencode/src/lsp/language.ts +++ b/packages/opencode/src/lsp/language.ts @@ -14,6 +14,7 @@ export const LANGUAGE_EXTENSIONS: Record = { ".cc": "cpp", ".c++": "cpp", ".cs": "csharp", + ".csx": "csharp", ".css": "css", ".d": "d", ".pas": "pascal", diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index a0cb8fe38..7faaeb42f 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -703,31 +703,10 @@ export const Zls: Info = { export const CSharp: Info = { id: "csharp", root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"]), - extensions: [".cs"], + extensions: [".cs", ".csx"], async spawn(root) { - let bin = which("roslyn-language-server") - if (!bin) { - if (!which("dotnet")) { - log.error(".NET SDK is required to install roslyn-language-server") - return - } - - if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - log.info("installing roslyn-language-server via dotnet tool") - const proc = Process.spawn(["dotnet", "tool", "install", "--global", "roslyn-language-server", "--prerelease"], { - stdout: "pipe", - stderr: "pipe", - stdin: "pipe", - }) - const exit = await proc.exited - if (exit !== 0) { - log.error("Failed to install roslyn-language-server") - return - } - - bin = path.join(Global.Path.bin, "roslyn-language-server" + (process.platform === "win32" ? ".exe" : "")) - log.info(`installed roslyn-language-server`, { bin }) - } + const bin = await getRoslynLanguageServer() + if (!bin) return return { process: spawn(bin, ["--stdio", "--autoLoadProjects"], { @@ -737,6 +716,135 @@ export const CSharp: Info = { }, } +export const Razor: Info = { + id: "razor", + root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"]), + extensions: [".razor", ".cshtml"], + async spawn(root) { + const bin = await getRoslynLanguageServer() + if (!bin) return + + const razor = await findVscodeRazorExtension() + if (!razor) { + log.info("VS Code C# extension with Razor support not found, skipping Razor LSP") + return + } + + log.info("using VS Code Razor extension for roslyn-language-server", { extension: razor.extension }) + return { + process: spawn( + bin, + [ + "--stdio", + "--autoLoadProjects", + `--razorSourceGenerator=${razor.compiler}`, + `--razorDesignTimePath=${razor.targets}`, + "--extension", + razor.extension, + ], + { + cwd: root, + }, + ), + } + }, +} + +let roslynLanguageServerInstall: Promise | undefined + +async function getRoslynLanguageServer() { + const existing = which("roslyn-language-server") + if (existing) return existing + + const global = await roslynLanguageServerGlobalPath() + if (global) return global + + roslynLanguageServerInstall ||= installRoslynLanguageServer().finally(() => { + roslynLanguageServerInstall = undefined + }) + return roslynLanguageServerInstall +} + +async function installRoslynLanguageServer() { + if (!which("dotnet")) { + log.error(".NET SDK is required to install roslyn-language-server") + return + } + + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return + log.info("installing roslyn-language-server via dotnet tool") + const proc = Process.spawn(["dotnet", "tool", "install", "--global", "roslyn-language-server", "--prerelease"], { + stdout: "pipe", + stderr: "pipe", + stdin: "pipe", + }) + const exit = await proc.exited + if (exit !== 0) { + log.error("Failed to install roslyn-language-server") + return + } + + const resolved = which("roslyn-language-server") + if (resolved) { + log.info(`installed roslyn-language-server`, { bin: resolved }) + return resolved + } + + const global = await roslynLanguageServerGlobalPath() + if (global) { + log.info(`installed roslyn-language-server`, { bin: global }) + return global + } + + log.error("Installed roslyn-language-server but could not resolve executable") +} + +async function roslynLanguageServerGlobalPath() { + const bin = path.join( + process.env.DOTNET_CLI_HOME ?? os.homedir(), + ".dotnet", + "tools", + "roslyn-language-server" + (process.platform === "win32" ? ".cmd" : ""), + ) + return (await pathExists(bin)) ? bin : undefined +} + +async function findVscodeRazorExtension() { + const roots = [ + process.env.VSCODE_EXTENSIONS, + path.join(os.homedir(), ".vscode", "extensions"), + path.join(os.homedir(), ".vscode-insiders", "extensions"), + path.join(os.homedir(), ".vscode-server", "extensions"), + path.join(os.homedir(), ".vscode-server-insiders", "extensions"), + ].filter((item) => item !== undefined) + + for (const root of [...new Set(roots)]) { + const entries = await fs.readdir(root, { withFileTypes: true }).catch(() => []) + const candidates = await Promise.all( + entries + .filter((entry) => entry.isDirectory() && entry.name.startsWith("ms-dotnettools.csharp-")) + .map(async (entry) => ({ + path: path.join(root, entry.name, ".razorExtension"), + modified: (await fs.stat(path.join(root, entry.name)).catch(() => undefined))?.mtimeMs ?? 0, + })), + ) + for (const entry of candidates.sort((a, b) => b.modified - a.modified).map((candidate) => candidate.path)) { + const result = { + compiler: path.join(entry, "Microsoft.CodeAnalysis.Razor.Compiler.dll"), + targets: path.join(entry, "Targets", "Microsoft.NET.Sdk.Razor.DesignTime.targets"), + extension: path.join(entry, "Microsoft.VisualStudioCode.RazorExtension.dll"), + } + if ( + (await pathExists(result.compiler)) && + (await pathExists(result.targets)) && + (await pathExists(result.extension)) + ) { + return result + } + } + } +} + export const FSharp: Info = { id: "fsharp", root: NearestRoot([".slnx", ".sln", ".fsproj", "global.json"]), diff --git a/packages/web/src/content/docs/lsp.mdx b/packages/web/src/content/docs/lsp.mdx index f242f4c5e..ad6a4644d 100644 --- a/packages/web/src/content/docs/lsp.mdx +++ b/packages/web/src/content/docs/lsp.mdx @@ -16,7 +16,7 @@ OpenCode comes with several built-in LSP servers for popular languages: | astro | .astro | Auto-installs for Astro projects | | bash | .sh, .bash, .zsh, .ksh | Auto-installs bash-language-server | | clangd | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | Auto-installs for C/C++ projects | -| csharp | .cs | `.NET SDK` installed | +| csharp | .cs, .csx | `.NET SDK` installed | | clojure-lsp | .clj, .cljs, .cljc, .edn | `clojure-lsp` command available | | dart | .dart | `dart` command available | | deno | .ts, .tsx, .js, .jsx, .mjs | `deno` command available (auto-detects deno.json/deno.jsonc) | @@ -36,6 +36,7 @@ OpenCode comes with several built-in LSP servers for popular languages: | php intelephense | .php | Auto-installs for PHP projects | | prisma | .prisma | `prisma` command available | | pyright | .py, .pyi | `pyright` dependency installed | +| razor | .razor, .cshtml | `.NET SDK` and VS Code C# extension installed | | ruby-lsp (rubocop) | .rb, .rake, .gemspec, .ru | `ruby` and `gem` commands available | | rust | .rs | `rust-analyzer` command available | | sourcekit-lsp | .swift, .objc, .objcpp | `swift` installed (`xcode` on macOS) | From 386091b79a76ebc49ce2eba9628a2f93ce0061ee Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Sat, 25 Apr 2026 10:32:41 +1000 Subject: [PATCH 142/426] fix: validate beta before pushing (#24230) --- script/beta.ts | 101 +++++++++++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 41 deletions(-) diff --git a/script/beta.ts b/script/beta.ts index 7c558d1e7..bed777e84 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -81,6 +81,39 @@ async function build() { } } +async function validate() { + if (!(await typecheck())) return false + if (!(await build())) return false + return true +} + +async function commitSmokeChanges() { + const out = await $`git status --porcelain`.text() + if (!out.trim()) { + console.log("Smoke check passed") + return true + } + + try { + await $`git add -A` + await $`git commit -m "Fix beta integration"` + } catch (err) { + console.log(`Failed to commit smoke fixes: ${err}`) + return false + } + + if (!(await validate())) return false + + const left = await $`git status --porcelain`.text() + if (!left.trim()) { + console.log("Smoke check passed") + return true + } + + console.log(`Smoke check left uncommitted changes:\n${left}`) + return false +} + async function install() { console.log(" Regenerating bun.lock...") @@ -143,11 +176,15 @@ async function fix(pr: PR, files: string[], prs: PR[], applied: number[], idx: n } async function smoke(prs: PR[], applied: number[]) { - console.log("\nRunning final smoke check with opencode...") + console.log("\nRunning final smoke check...") + + if (await validate()) return commitSmokeChanges() + + console.log("\nTrying to fix final smoke check with opencode...") const done = lines(prs.filter((x) => applied.includes(x.number))) const prompt = [ - "The beta merge batch is complete.", + "The beta merge batch is complete, but the deterministic final smoke check failed.", `Merged PRs on HEAD:\n${done}`, "Run `bun typecheck` at the repo root.", "Run `./script/build.ts --single` in `packages/opencode`.", @@ -162,38 +199,8 @@ async function smoke(prs: PR[], applied: number[]) { return false } - if (!(await typecheck())) { - return false - } - - if (!(await build())) { - return false - } - - const out = await $`git status --porcelain`.text() - if (!out.trim()) { - console.log("Smoke check passed") - return true - } - - try { - await $`git add -A` - await $`git commit -m "Fix beta integration"` - } catch (err) { - console.log(`Failed to commit smoke fixes: ${err}`) - return false - } - - if (!(await typecheck())) { - return false - } - - if (!(await build())) { - return false - } - - console.log("Smoke check passed") - return true + if (!(await validate())) return false + return commitSmokeChanges() } async function main() { @@ -294,17 +301,13 @@ async function main() { throw new Error(`${failed.length} PR(s) failed to merge`) } - if (applied.length > 0) { - console.log("\nSkipping final smoke check") - } - console.log("\nChecking if beta branch has changes...") await $`git fetch origin beta` - const localTree = await $`git rev-parse beta^{tree}`.text() + const localTree = (await $`git rev-parse beta^{tree}`.text()).trim() const remoteTrees = (await $`git log origin/dev..origin/beta --format=%T`.text()).split("\n") - const matchIdx = remoteTrees.indexOf(localTree.trim()) + const matchIdx = remoteTrees.indexOf(localTree) if (matchIdx !== -1) { if (matchIdx !== 0) { console.log(`Beta branch contains this sync, but additional commits exist after it. Leaving beta branch as is.`) @@ -314,7 +317,23 @@ async function main() { return } - console.log("Force pushing beta branch...") + if (!(await smoke(prs, applied))) throw new Error("Final smoke check failed") + + await $`git fetch origin beta` + + const validatedTree = (await $`git rev-parse beta^{tree}`.text()).trim() + const remoteTreesAfterSmoke = (await $`git log origin/dev..origin/beta --format=%T`.text()).split("\n") + const matchIdxAfterSmoke = remoteTreesAfterSmoke.indexOf(validatedTree) + if (matchIdxAfterSmoke !== -1) { + if (matchIdxAfterSmoke !== 0) { + console.log(`Beta branch contains this validated sync, but additional commits exist after it. Leaving beta branch as is.`) + } else { + console.log("Validated beta branch now matches remote contents, no push needed") + } + return + } + + console.log("Force pushing validated beta branch...") await $`git push origin beta --force --no-verify` console.log("Successfully synced beta branch") From ec201623fb1fcbcc709d095a7bbaeb84267ee625 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 00:34:02 +0000 Subject: [PATCH 143/426] chore: generate --- script/beta.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/script/beta.ts b/script/beta.ts index bed777e84..1f6b6ba52 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -326,7 +326,9 @@ async function main() { const matchIdxAfterSmoke = remoteTreesAfterSmoke.indexOf(validatedTree) if (matchIdxAfterSmoke !== -1) { if (matchIdxAfterSmoke !== 0) { - console.log(`Beta branch contains this validated sync, but additional commits exist after it. Leaving beta branch as is.`) + console.log( + `Beta branch contains this validated sync, but additional commits exist after it. Leaving beta branch as is.`, + ) } else { console.log("Validated beta branch now matches remote contents, no push needed") } From cdc7d5f2eafa62158a6305025d4c1b37d961c40f Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Sat, 25 Apr 2026 10:42:33 +1000 Subject: [PATCH 144/426] chore: group beta PR logs (#24236) --- script/beta.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/script/beta.ts b/script/beta.ts index 1f6b6ba52..d738c36ff 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -57,6 +57,19 @@ function lines(prs: PR[]) { return prs.map((x) => `- #${x.number}: ${x.title}`).join("\n") || "(none)" } +function group(title: string) { + if (process.env.GITHUB_ACTIONS !== "true") { + console.log(title) + return { [Symbol.dispose]() {} } + } + console.log(`::group::${title}`) + return { + [Symbol.dispose]() { + console.log("::endgroup::") + }, + } +} + async function typecheck() { console.log(" Running typecheck...") @@ -227,8 +240,8 @@ async function main() { const failed: FailedPR[] = [] for (const [idx, pr] of prs.entries()) { - console.log(`\nProcessing PR ${idx + 1}/${prs.length} #${pr.number}: ${pr.title}`) - + console.log() + using _ = group(`Processing PR ${idx + 1}/${prs.length} #${pr.number}: ${pr.title}`) console.log(" Fetching PR head...") try { await $`git fetch origin pull/${pr.number}/head:pr/${pr.number}` From 49894330d91c981e35be2002079442c8d0fe9836 Mon Sep 17 00:00:00 2001 From: Maddison Hellstrom Date: Fri, 24 Apr 2026 19:47:05 -0700 Subject: [PATCH 145/426] fix(build): add prettier to devDependencies (#23255) --- bun.lock | 1 + packages/opencode/package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/bun.lock b/bun.lock index d42183f93..47d178706 100644 --- a/bun.lock +++ b/bun.lock @@ -451,6 +451,7 @@ "@typescript/native-preview": "catalog:", "drizzle-kit": "catalog:", "drizzle-orm": "catalog:", + "prettier": "3.6.2", "typescript": "catalog:", "vscode-languageserver-types": "3.17.5", "why-is-node-running": "3.2.2", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 14ae8b72e..880459e96 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -69,6 +69,7 @@ "@typescript/native-preview": "catalog:", "drizzle-kit": "catalog:", "drizzle-orm": "catalog:", + "prettier": "3.6.2", "typescript": "catalog:", "vscode-languageserver-types": "3.17.5", "why-is-node-running": "3.2.2", From e29058c346e50976c6b5a2277f22d1902917e65c Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 03:04:44 +0000 Subject: [PATCH 146/426] 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 07d53e889..30a2bbfbd 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-+G3/s18NZO1Dpc5TsZyix2Npodzei25Svw3nTjfzXW8=", - "aarch64-linux": "sha256-39HPencmRYRbyCk/cZIdPFk6ocY1AMlyuN9j25zAKzI=", - "aarch64-darwin": "sha256-043korPEjSHKiZ3P+EfWyOfKpgOC7CBpviccviaDa0o=", - "x86_64-darwin": "sha256-vsZ7e//rL9e7Cl5kl/Xplvi1fqayljxTLwRSbxvCxeM=" + "x86_64-linux": "sha256-V1Rt2k7ujkqGw4pDkn++WALTy1fAugvoKLhKvwFKkss=", + "aarch64-linux": "sha256-ho0AuGbJ1qw9Hvb3EbGC8f0lWqqgUslvda/wTe32MFo=", + "aarch64-darwin": "sha256-hdUyNmp+snwtnBckHXsPMgNFUYS1sYDdngkk+AXVqzc=", + "x86_64-darwin": "sha256-P57LpQNF8fplFKQBBIukhOKbIugbViyBUIUjClXohuk=" } } From f7d527cd28affbd68c18e11c36799d252d88df13 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:47:36 -0400 Subject: [PATCH 147/426] ci: adjust auto close issue script to use not planned instead of completed (#24253) --- script/github/close-issues.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/github/close-issues.ts b/script/github/close-issues.ts index e8f0573eb..9e1f59795 100755 --- a/script/github/close-issues.ts +++ b/script/github/close-issues.ts @@ -37,7 +37,7 @@ async function close(num: number) { const patch = await fetch(base, { method: "PATCH", headers, - body: JSON.stringify({ state: "closed", state_reason: "completed" }), + body: JSON.stringify({ state: "closed", state_reason: "not_planned" }), }) if (!patch.ok) throw new Error(`Failed to close #${num}: ${patch.status} ${patch.statusText}`) From 4877eccc0d06c747624bf61aaa6f3e65cea9cc8d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 01:14:52 -0400 Subject: [PATCH 148/426] Fix shell cwd after login startup (#24215) --- packages/opencode/src/session/prompt.ts | 12 ++++++---- packages/opencode/test/session/prompt.test.ts | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 5f3530bce..3d07a96ec 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -787,6 +787,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the const shellName = ( process.platform === "win32" ? path.win32.basename(sh, ".exe") : path.basename(sh) ).toLowerCase() + const cwd = ctx.directory const invocations: Record = { nu: { args: ["-c", input.command] }, fish: { args: ["-c", input.command] }, @@ -795,12 +796,13 @@ NOTE: At any point in time through this workflow you should feel free to ask the "-l", "-c", ` - __oc_cwd=$PWD [[ -f ~/.zshenv ]] && source ~/.zshenv >/dev/null 2>&1 || true [[ -f "\${ZDOTDIR:-$HOME}/.zshrc" ]] && source "\${ZDOTDIR:-$HOME}/.zshrc" >/dev/null 2>&1 || true - cd "$__oc_cwd" + cd -- "$1" eval ${JSON.stringify(input.command)} `, + "opencode", + cwd, ], }, bash: { @@ -808,12 +810,13 @@ NOTE: At any point in time through this workflow you should feel free to ask the "-l", "-c", ` - __oc_cwd=$PWD shopt -s expand_aliases [[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true - cd "$__oc_cwd" + cd -- "$1" eval ${JSON.stringify(input.command)} `, + "opencode", + cwd, ], }, cmd: { args: ["/c", input.command] }, @@ -823,7 +826,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the } const args = (invocations[shellName] ?? invocations[""]).args - const cwd = ctx.directory const shellEnv = yield* plugin.trigger( "shell.env", { cwd, sessionID: input.sessionID, callID: part.callID }, diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 8ffb20f15..911cb4415 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1078,6 +1078,30 @@ unix("shell completes a fast command on the preferred shell", () => ), ) +unix("shell commands can change directory after startup", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const { prompt, run, chat } = yield* boot() + const parent = path.dirname(dir) + const result = yield* prompt.shell({ + sessionID: chat.id, + agent: "build", + command: "cd .. && pwd", + }) + + expect(result.info.role).toBe("assistant") + const tool = completedTool(result.parts) + if (!tool) return + + expect(tool.state.output).toContain(parent) + expect(tool.state.metadata.output).toContain(parent) + yield* run.assertNotBusy(chat.id) + }), + { git: true, config: cfg }, + ), +) + unix("shell lists files from the project directory", () => provideTmpdirInstance( (dir) => From 9ff999cc2b468ac7cee5747a95cd2195f4328aea Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Sat, 25 Apr 2026 14:21:35 +0200 Subject: [PATCH 149/426] tool/lsp: include request details in permission metadata (#24139) --- packages/opencode/src/tool/lsp.ts | 24 +++- packages/opencode/test/tool/lsp.test.ts | 162 ++++++++++++++++++++++++ 2 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 packages/opencode/test/tool/lsp.test.ts diff --git a/packages/opencode/src/tool/lsp.ts b/packages/opencode/src/tool/lsp.ts index 29c6a8d84..3bcae426a 100644 --- a/packages/opencode/src/tool/lsp.ts +++ b/packages/opencode/src/tool/lsp.ts @@ -36,7 +36,6 @@ export const LspTool = Tool.define( Effect.gen(function* () { const lsp = yield* LSP.Service const fs = yield* AppFileSystem.Service - return { description: DESCRIPTION, parameters: Parameters, @@ -47,12 +46,29 @@ export const LspTool = Tool.define( Effect.gen(function* () { const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath) yield* assertExternalDirectoryEffect(ctx, file) - yield* ctx.ask({ permission: "lsp", patterns: ["*"], always: ["*"], metadata: {} }) + const meta = + args.operation === "workspaceSymbol" + ? { operation: args.operation } + : args.operation === "documentSymbol" + ? { operation: args.operation, filePath: file } + : { operation: args.operation, filePath: file, line: args.line, character: args.character } + yield* ctx.ask({ + permission: "lsp", + patterns: ["*"], + always: ["*"], + metadata: meta, + }) const uri = pathToFileURL(file).href const position = { file, line: args.line - 1, character: args.character - 1 } const relPath = path.relative(Instance.worktree, file) - const title = `${args.operation} ${relPath}:${args.line}:${args.character}` + const detail = + args.operation === "workspaceSymbol" + ? "" + : args.operation === "documentSymbol" + ? relPath + : `${relPath}:${args.line}:${args.character}` + const title = detail ? `${args.operation} ${detail}` : args.operation const exists = yield* fs.existsSafe(file) if (!exists) throw new Error(`File not found: ${file}`) @@ -90,7 +106,7 @@ export const LspTool = Tool.define( metadata: { result }, output: result.length === 0 ? `No results found for ${args.operation}` : JSON.stringify(result, null, 2), } - }), + }).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/test/tool/lsp.test.ts b/packages/opencode/test/tool/lsp.test.ts new file mode 100644 index 000000000..57b8fc6e8 --- /dev/null +++ b/packages/opencode/test/tool/lsp.test.ts @@ -0,0 +1,162 @@ +import { afterEach, describe, expect } from "bun:test" +import { Effect, Layer } from "effect" +import path from "path" +import { Agent } from "../../src/agent/agent" +import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { LSP } from "../../src/lsp" +import { Permission } from "../../src/permission" +import { Instance } from "../../src/project/instance" +import { MessageID, SessionID } from "../../src/session/schema" +import { Tool, Truncate } from "../../src/tool" +import { LspTool } from "../../src/tool/lsp" +import { provideTmpdirInstance } from "../fixture/fixture" +import { testEffect } from "../lib/effect" + +afterEach(async () => { + await Instance.disposeAll() +}) + +const ctx = { + sessionID: SessionID.make("ses_test"), + messageID: MessageID.make(""), + callID: "", + agent: "build", + abort: AbortSignal.any([]), + messages: [], + metadata: () => Effect.void, + ask: () => Effect.void, +} + +const lsp = Layer.succeed( + LSP.Service, + LSP.Service.of({ + init: () => Effect.void, + status: () => Effect.succeed([]), + hasClients: () => Effect.succeed(true), + touchFile: () => Effect.void, + diagnostics: () => Effect.succeed({}), + hover: () => Effect.succeed([]), + definition: () => Effect.succeed([]), + references: () => Effect.succeed([]), + implementation: () => Effect.succeed([]), + documentSymbol: () => Effect.succeed([]), + workspaceSymbol: () => Effect.succeed([]), + prepareCallHierarchy: () => Effect.succeed([]), + incomingCalls: () => Effect.succeed([]), + outgoingCalls: () => Effect.succeed([]), + }), +) + +const it = testEffect( + Layer.mergeAll( + Agent.defaultLayer, + AppFileSystem.defaultLayer, + CrossSpawnSpawner.defaultLayer, + Truncate.defaultLayer, + lsp, + ), +) + +const init = Effect.fn("LspToolTest.init")(function* () { + const info = yield* LspTool + return yield* info.init() +}) + +const run = Effect.fn("LspToolTest.run")(function* ( + args: Tool.InferParameters, + next: Tool.Context = ctx, +) { + const tool = yield* init() + return yield* tool.execute(args, next) +}) + +const put = Effect.fn("LspToolTest.put")(function* (file: string) { + const fs = yield* AppFileSystem.Service + yield* fs.writeWithDirs(file, "export const x = 1\n") +}) + +const asks = () => { + const items: Array> = [] + return { + items, + next: { + ...ctx, + ask: (req: Omit) => + Effect.sync(() => { + items.push(req) + }), + }, + } +} + +describe("tool.lsp", () => { + describe("permission metadata", () => { + it.live("keeps cursor details for position-based operations", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const file = path.join(dir, "test.ts") + yield* put(file) + + const { items, next } = asks() + const result = yield* run({ operation: "goToDefinition", filePath: file, line: 3, character: 7 }, next) + const req = items.find((item) => item.permission === "lsp") + + expect(req).toBeDefined() + expect(req!.metadata).toEqual({ + operation: "goToDefinition", + filePath: file, + line: 3, + character: 7, + }) + expect(result.title).toBe("goToDefinition test.ts:3:7") + }), + { git: true }, + ), + ) + + it.live("omits cursor details for documentSymbol", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const file = path.join(dir, "test.ts") + yield* put(file) + + const { items, next } = asks() + const result = yield* run({ operation: "documentSymbol", filePath: file, line: 3, character: 7 }, next) + const req = items.find((item) => item.permission === "lsp") + + expect(req).toBeDefined() + expect(req!.metadata).toEqual({ + operation: "documentSymbol", + filePath: file, + }) + expect(result.title).toBe("documentSymbol test.ts") + }), + { git: true }, + ), + ) + + it.live("omits file and cursor details for workspaceSymbol", () => + provideTmpdirInstance( + (dir) => + Effect.gen(function* () { + const file = path.join(dir, "test.ts") + yield* put(file) + + const { items, next } = asks() + const result = yield* run({ operation: "workspaceSymbol", filePath: file, line: 3, character: 7 }, next) + const req = items.find((item) => item.permission === "lsp") + + expect(req).toBeDefined() + expect(req!.metadata).toEqual({ + operation: "workspaceSymbol", + }) + expect(result.title).toBe("workspaceSymbol") + }), + { git: true }, + ), + ) + }) +}) From 66f93035b03a2779a374c608a57dec5d30f35df1 Mon Sep 17 00:00:00 2001 From: Dax Date: Sat, 25 Apr 2026 09:18:42 -0400 Subject: [PATCH 150/426] fix permission config order (#24222) --- packages/opencode/src/config/permission.ts | 30 ++++++++----- packages/opencode/src/permission/index.ts | 12 +---- packages/opencode/test/config/config.test.ts | 24 +++------- .../opencode/test/permission/next.test.ts | 44 ++++++------------- 4 files changed, 40 insertions(+), 70 deletions(-) diff --git a/packages/opencode/src/config/permission.ts b/packages/opencode/src/config/permission.ts index fdd574683..909112c7c 100644 --- a/packages/opencode/src/config/permission.ts +++ b/packages/opencode/src/config/permission.ts @@ -1,6 +1,7 @@ export * as ConfigPermission from "./permission" import { Schema, SchemaGetter } from "effect" -import { zod } from "@/util/effect-zod" +import z from "zod" +import { ZodOverride, zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" export const Action = Schema.Literals(["ask", "allow", "deny"]) @@ -18,17 +19,9 @@ export const Rule = Schema.Union([Action, Object]) .pipe(withStatics((s) => ({ zod: zod(s) }))) export type Rule = Schema.Schema.Type -// Known permission keys get explicit types — most are full Rule (either a -// single Action or a per-pattern object), but a handful of tools take no -// sub-target patterns and are Action-only. Unknown keys fall through the -// Record rest signature as Rule. -// -// StructWithRest canonicalises key order on decode (known first, then rest), -// which used to require the `__originalKeys` preprocess hack because -// `Permission.fromConfig` depended on the user's insertion order. That -// dependency is gone — `fromConfig` now sorts top-level keys so wildcard -// permissions come before specifics, making the final precedence -// order-independent. +// Known permission keys get explicit types in the Effect schema for generated +// docs/types. Runtime config parsing uses `InfoZod` below so user key order is +// preserved for permission precedence. const InputObject = Schema.StructWithRest( Schema.Struct({ read: Schema.optional(Rule), @@ -60,6 +53,18 @@ const InputSchema = Schema.Union([Action, InputObject]) const normalizeInput = (input: Schema.Schema.Type): Schema.Schema.Type => typeof input === "string" ? { "*": input } : input +const ACTION_ONLY = new Set(["todowrite", "question", "webfetch", "websearch", "codesearch", "doom_loop"]) + +const InfoZod = z + .union([zod(Action), z.record(z.string(), z.union([zod(Action), z.record(z.string(), zod(Action))]))]) + .transform(normalizeInput) + .superRefine((input, ctx) => { + for (const [key, value] of globalThis.Object.entries(input)) { + if (!ACTION_ONLY.has(key) || typeof value === "string") continue + ctx.addIssue({ code: "custom", message: `${key} must be a permission action`, path: [key] }) + } + }) + export const Info = InputSchema.pipe( Schema.decodeTo(InputObject, { decode: SchemaGetter.transform(normalizeInput), @@ -70,6 +75,7 @@ export const Info = InputSchema.pipe( }), ) .annotate({ identifier: "PermissionConfig" }) + .annotate({ [ZodOverride]: InfoZod }) .pipe( // Walker already emits the decodeTo transform into the derived zod (see // `encoded()` in effect-zod.ts), so just expose that directly. diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 05c832016..428514ecd 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -288,18 +288,8 @@ function expand(pattern: string): string { } export function fromConfig(permission: ConfigPermission.Info) { - // Sort top-level keys so wildcard permissions (`*`, `mcp_*`) come before - // specific ones. Combined with `findLast` in evaluate(), this gives the - // intuitive semantic "specific tool rules override the `*` fallback" - // regardless of the user's JSON key order. Sub-pattern order inside a - // single permission key is preserved — only top-level keys are sorted. - const entries = Object.entries(permission).sort(([a], [b]) => { - const aWild = a.includes("*") - const bWild = b.includes("*") - return aWild === bWild ? 0 : aWild ? -1 : 1 - }) const ruleset: Ruleset = [] - for (const [key, value] of entries) { + for (const [key, value] of Object.entries(permission)) { if (typeof value === "string") { ruleset.push({ permission: key, action: value, pattern: "*" }) continue diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 73dd46e31..361ac0b5d 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1495,16 +1495,9 @@ test("merges legacy tools with existing permission config", async () => { }) }) -test("permission config canonicalises known keys first, preserves rest-key insertion order", async () => { - // ConfigPermission.Info is a StructWithRest schema — the decoder reorders - // keys into declaration-order for known permission names (edit, read, - // todowrite, external_directory are declared in `config/permission.ts`), - // followed by rest keys in the user's insertion order. - // - // Rule precedence is NOT affected by this reordering: `Permission.fromConfig` - // sorts wildcards before specifics before iterating. See the - // "fromConfig - specific key beats wildcard regardless of JSON key order" - // test in test/permission/next.test.ts for the behavioural guarantee. +test("permission config preserves user key order", async () => { + // Permission precedence follows the order users write in config, so parsing + // must not canonicalise known keys ahead of wildcard or custom keys. await using tmp = await tmpdir({ init: async (dir) => { await Filesystem.write( @@ -1532,15 +1525,12 @@ test("permission config canonicalises known keys first, preserves rest-key inser fn: async () => { const config = await load() expect(Object.keys(config.permission!)).toEqual([ - // known fields that the user provided, in declaration order from - // config/permission.ts (read, edit, ..., external_directory, todowrite) - "read", - "edit", - "external_directory", - "todowrite", - // rest keys (not in the known list), in user's insertion order "*", + "edit", "write", + "external_directory", + "read", + "todowrite", "thoughts_*", "reasoning_model_*", "tools_*", diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index 372e1be7e..b58c716d8 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -128,61 +128,45 @@ test("fromConfig - does not expand tilde in middle of path", () => { expect(result).toEqual([{ permission: "external_directory", pattern: "/some/~/path", action: "allow" }]) }) -// Top-level wildcard-vs-specific precedence semantics. -// -// fromConfig sorts top-level keys so wildcard permissions (containing "*") -// come before specific permissions. Combined with `findLast` in evaluate(), -// this gives the intuitive semantic "specific tool rules override the `*` -// fallback", regardless of the order the user wrote the keys in their JSON. -// -// Sub-pattern order inside a single permission key (e.g. `bash: { "*": "allow", "rm": "deny" }`) -// still depends on insertion order — only top-level keys are sorted. +// Permission precedence follows config insertion order. `evaluate()` uses the +// last matching rule, so later config entries intentionally override earlier +// entries even when a wildcard appears after a specific permission. -test("fromConfig - specific key beats wildcard regardless of JSON key order", () => { +test("fromConfig - preserves top-level config key order", () => { const wildcardFirst = Permission.fromConfig({ "*": "deny", bash: "allow" }) const specificFirst = Permission.fromConfig({ bash: "allow", "*": "deny" }) - // Both orderings produce the same ruleset - expect(wildcardFirst).toEqual(specificFirst) + expect(wildcardFirst.map((r) => r.permission)).toEqual(["*", "bash"]) + expect(specificFirst.map((r) => r.permission)).toEqual(["bash", "*"]) - // And both evaluate bash → allow (bash rule wins over * fallback) expect(Permission.evaluate("bash", "ls", wildcardFirst).action).toBe("allow") - expect(Permission.evaluate("bash", "ls", specificFirst).action).toBe("allow") + expect(Permission.evaluate("bash", "ls", specificFirst).action).toBe("deny") }) -test("fromConfig - wildcard acts as fallback for permissions with no specific rule", () => { - const ruleset = Permission.fromConfig({ bash: "allow", "*": "ask" }) +test("fromConfig - wildcard acts as fallback when it appears before specifics", () => { + const ruleset = Permission.fromConfig({ "*": "ask", bash: "allow" }) expect(Permission.evaluate("edit", "foo.ts", ruleset).action).toBe("ask") expect(Permission.evaluate("bash", "ls", ruleset).action).toBe("allow") }) -test("fromConfig - top-level ordering: wildcards first, specifics after", () => { +test("fromConfig - top-level ordering is not sorted by wildcard specificity", () => { const ruleset = Permission.fromConfig({ bash: "allow", "*": "ask", edit: "deny", "mcp_*": "allow", }) - // wildcards (* and mcp_*) come before specifics (bash, edit) - const permissions = ruleset.map((r) => r.permission) - expect(permissions.slice(0, 2).sort()).toEqual(["*", "mcp_*"]) - expect(permissions.slice(2)).toEqual(["bash", "edit"]) + expect(ruleset.map((r) => r.permission)).toEqual(["bash", "*", "edit", "mcp_*"]) }) -test("fromConfig - sub-pattern insertion order inside a tool key is preserved (only top-level sorts)", () => { - // Sub-patterns within a single tool key use the documented "`*` first, - // specific patterns after" convention (findLast picks specifics). The - // top-level sort must not touch sub-pattern ordering. +test("fromConfig - sub-pattern insertion order inside a tool key is preserved", () => { const ruleset = Permission.fromConfig({ bash: { "*": "deny", "git *": "allow" } }) expect(ruleset.map((r) => r.pattern)).toEqual(["*", "git *"]) - // * fallback for unknown commands expect(Permission.evaluate("bash", "rm foo", ruleset).action).toBe("deny") - // specific pattern wins for git commands (it's last, findLast picks it) expect(Permission.evaluate("bash", "git status", ruleset).action).toBe("allow") }) -test("fromConfig - canonical documented example unchanged", () => { - // Regression guard for the example in docs/permissions.mdx +test("fromConfig - documented fallback-first example", () => { const ruleset = Permission.fromConfig({ "*": "ask", bash: "allow", edit: "deny" }) expect(Permission.evaluate("bash", "ls", ruleset).action).toBe("allow") expect(Permission.evaluate("edit", "foo.ts", ruleset).action).toBe("deny") @@ -448,7 +432,7 @@ test("evaluate - wildcard permission fallback for unknown tool", () => { expect(result.action).toBe("ask") }) -test("evaluate - permission patterns sorted by length regardless of object order", () => { +test("evaluate - later wildcard permission can override earlier specific permission", () => { const result = Permission.evaluate("bash", "rm", [ { permission: "bash", pattern: "*", action: "allow" }, { permission: "*", pattern: "*", action: "deny" }, From fc88ed1262c78e7ad75ca564a5f9e970c7dae00f Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 13:19:42 +0000 Subject: [PATCH 151/426] chore: generate --- packages/sdk/js/src/v2/gen/types.gen.ts | 28 ++------ packages/sdk/openapi.json | 85 +++++-------------------- 2 files changed, 21 insertions(+), 92 deletions(-) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 40e661b46..51a79d99d 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1198,32 +1198,14 @@ export type ServerConfig = { export type PermissionActionConfig = "ask" | "allow" | "deny" -export type PermissionObjectConfig = { - [key: string]: PermissionActionConfig -} - -export type PermissionRuleConfig = PermissionActionConfig | PermissionObjectConfig - export type PermissionConfig = | PermissionActionConfig | { - read?: PermissionRuleConfig - edit?: PermissionRuleConfig - glob?: PermissionRuleConfig - grep?: PermissionRuleConfig - list?: PermissionRuleConfig - bash?: PermissionRuleConfig - task?: PermissionRuleConfig - external_directory?: PermissionRuleConfig - todowrite?: PermissionActionConfig - question?: PermissionActionConfig - webfetch?: PermissionActionConfig - websearch?: PermissionActionConfig - codesearch?: PermissionActionConfig - lsp?: PermissionRuleConfig - doom_loop?: PermissionActionConfig - skill?: PermissionRuleConfig - [key: string]: PermissionRuleConfig | PermissionActionConfig | undefined + [key: string]: + | PermissionActionConfig + | { + [key: string]: PermissionActionConfig + } } export type AgentConfig = { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index cd7b381d8..cbb9aaecc 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -10926,25 +10926,6 @@ "type": "string", "enum": ["ask", "allow", "deny"] }, - "PermissionObjectConfig": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/components/schemas/PermissionActionConfig" - } - }, - "PermissionRuleConfig": { - "anyOf": [ - { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - { - "$ref": "#/components/schemas/PermissionObjectConfig" - } - ] - }, "PermissionConfig": { "anyOf": [ { @@ -10952,58 +10933,24 @@ }, { "type": "object", - "properties": { - "read": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "edit": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "glob": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "grep": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "list": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "bash": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "task": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "external_directory": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "todowrite": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "question": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "webfetch": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "websearch": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "codesearch": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "lsp": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "doom_loop": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "skill": { - "$ref": "#/components/schemas/PermissionRuleConfig" - } + "propertyNames": { + "type": "string" }, "additionalProperties": { - "$ref": "#/components/schemas/PermissionRuleConfig" + "anyOf": [ + { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/PermissionActionConfig" + } + } + ] } } ] From d748c718457f64f6eeef02f406ebca67f78864fa Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 25 Apr 2026 09:41:30 -0400 Subject: [PATCH 152/426] ci: centralize opentui dependencies in workspace catalog Use catalog references for @opentui/core, @opentui/solid, and opentui-spinner across packages to ensure consistent versions and simplify updates. --- bun.lock | 15 ++++++++------- package.json | 5 +++-- packages/opencode/package.json | 6 +++--- packages/plugin/package.json | 4 ++-- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/bun.lock b/bun.lock index 47d178706..d0640276b 100644 --- a/bun.lock +++ b/bun.lock @@ -367,8 +367,8 @@ "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", - "@opentui/core": "0.1.103", - "@opentui/solid": "0.1.103", + "@opentui/core": "catalog:", + "@opentui/solid": "catalog:", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -403,7 +403,7 @@ "open": "10.1.2", "opencode-gitlab-auth": "2.0.1", "opencode-poe-auth": "0.0.1", - "opentui-spinner": "0.0.6", + "opentui-spinner": "catalog:", "partial-json": "0.1.7", "remeda": "catalog:", "semver": "^7.6.3", @@ -467,8 +467,8 @@ "zod": "catalog:", }, "devDependencies": { - "@opentui/core": "0.1.103", - "@opentui/solid": "0.1.103", + "@opentui/core": "catalog:", + "@opentui/solid": "catalog:", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "@typescript/native-preview": "catalog:", @@ -678,8 +678,8 @@ "@npmcli/arborist": "9.4.0", "@octokit/rest": "22.0.0", "@openauthjs/openauth": "0.0.0-20250322224806", - "@opentui/core": "0.1.99", - "@opentui/solid": "0.1.99", + "@opentui/core": "0.1.103", + "@opentui/solid": "0.1.103", "@pierre/diffs": "1.1.0-beta.18", "@playwright/test": "1.59.1", "@solid-primitives/storage": "4.3.3", @@ -708,6 +708,7 @@ "luxon": "3.6.1", "marked": "17.0.1", "marked-shiki": "1.2.1", + "opentui-spinner": "0.0.6", "remeda": "2.26.0", "remend": "1.3.0", "semver": "7.7.4", diff --git a/package.json b/package.json index f918bcd02..b2c8a2d7a 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "@types/cross-spawn": "6.0.6", "@octokit/rest": "22.0.0", "@hono/zod-validator": "0.4.2", - "@opentui/core": "0.1.99", - "@opentui/solid": "0.1.99", + "@opentui/core": "0.1.103", + "@opentui/solid": "0.1.103", "ulid": "3.0.1", "@kobalte/core": "0.13.11", "@types/luxon": "3.7.1", @@ -46,6 +46,7 @@ "@cloudflare/workers-types": "4.20251008.0", "@openauthjs/openauth": "0.0.0-20250322224806", "@pierre/diffs": "1.1.0-beta.18", + "opentui-spinner": "0.0.6", "@solid-primitives/storage": "4.3.3", "@tailwindcss/vite": "4.1.11", "diff": "8.0.2", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 880459e96..8d54ad2e7 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -124,8 +124,8 @@ "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", - "@opentui/core": "0.1.103", - "@opentui/solid": "0.1.103", + "@opentui/core": "catalog:", + "@opentui/solid": "catalog:", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -160,7 +160,7 @@ "open": "10.1.2", "opencode-gitlab-auth": "2.0.1", "opencode-poe-auth": "0.0.1", - "opentui-spinner": "0.0.6", + "opentui-spinner": "catalog:", "partial-json": "0.1.7", "remeda": "catalog:", "semver": "^7.6.3", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index e7d9b87ed..3678ccd25 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -34,8 +34,8 @@ } }, "devDependencies": { - "@opentui/core": "0.1.103", - "@opentui/solid": "0.1.103", + "@opentui/core": "catalog:", + "@opentui/solid": "catalog:", "@tsconfig/node22": "catalog:", "@types/node": "catalog:", "typescript": "catalog:", From 1b92c95425e5725b9908e13c92b98201e8debe8d Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 25 Apr 2026 09:48:09 -0400 Subject: [PATCH 153/426] core: permission config schema now provides full IntelliSense for all tool permission keys The permission configuration previously used a generic record type that didn't offer editor completions. Updated the schema to explicitly list all tool permission keys (read, edit, glob, grep, list, bash, task, external_directory, lsp, skill, todowrite, question, webfetch, websearch, codesearch, doom_loop) with proper types, enabling autocomplete when editing permission files. --- packages/opencode/src/config/permission.ts | 35 +++++-- packages/sdk/js/src/v2/gen/types.gen.ts | 34 +++++-- packages/sdk/openapi.json | 102 +++++++++++++++++---- 3 files changed, 137 insertions(+), 34 deletions(-) diff --git a/packages/opencode/src/config/permission.ts b/packages/opencode/src/config/permission.ts index 909112c7c..a7390e953 100644 --- a/packages/opencode/src/config/permission.ts +++ b/packages/opencode/src/config/permission.ts @@ -53,17 +53,34 @@ const InputSchema = Schema.Union([Action, InputObject]) const normalizeInput = (input: Schema.Schema.Type): Schema.Schema.Type => typeof input === "string" ? { "*": input } : input -const ACTION_ONLY = new Set(["todowrite", "question", "webfetch", "websearch", "codesearch", "doom_loop"]) - const InfoZod = z - .union([zod(Action), z.record(z.string(), z.union([zod(Action), z.record(z.string(), zod(Action))]))]) + .union([ + zod(Action), + z.intersection( + z.record(z.string(), zod(Rule)), + z + .object({ + read: zod(Rule).optional(), + edit: zod(Rule).optional(), + glob: zod(Rule).optional(), + grep: zod(Rule).optional(), + list: zod(Rule).optional(), + bash: zod(Rule).optional(), + task: zod(Rule).optional(), + external_directory: zod(Rule).optional(), + todowrite: zod(Action).optional(), + question: zod(Action).optional(), + webfetch: zod(Action).optional(), + websearch: zod(Action).optional(), + codesearch: zod(Action).optional(), + lsp: zod(Rule).optional(), + doom_loop: zod(Action).optional(), + skill: zod(Rule).optional(), + }) + .catchall(zod(Rule)), + ), + ]) .transform(normalizeInput) - .superRefine((input, ctx) => { - for (const [key, value] of globalThis.Object.entries(input)) { - if (!ACTION_ONLY.has(key) || typeof value === "string") continue - ctx.addIssue({ code: "custom", message: `${key} must be a permission action`, path: [key] }) - } - }) export const Info = InputSchema.pipe( Schema.decodeTo(InputObject, { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 51a79d99d..0ad88bb50 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1198,15 +1198,35 @@ export type ServerConfig = { export type PermissionActionConfig = "ask" | "allow" | "deny" +export type PermissionObjectConfig = { + [key: string]: PermissionActionConfig +} + +export type PermissionRuleConfig = PermissionActionConfig | PermissionObjectConfig + export type PermissionConfig = | PermissionActionConfig - | { - [key: string]: - | PermissionActionConfig - | { - [key: string]: PermissionActionConfig - } - } + | ({ + [key: string]: PermissionRuleConfig + } & { + read?: PermissionRuleConfig + edit?: PermissionRuleConfig + glob?: PermissionRuleConfig + grep?: PermissionRuleConfig + list?: PermissionRuleConfig + bash?: PermissionRuleConfig + task?: PermissionRuleConfig + external_directory?: PermissionRuleConfig + todowrite?: PermissionActionConfig + question?: PermissionActionConfig + webfetch?: PermissionActionConfig + websearch?: PermissionActionConfig + codesearch?: PermissionActionConfig + lsp?: PermissionRuleConfig + doom_loop?: PermissionActionConfig + skill?: PermissionRuleConfig + [key: string]: PermissionRuleConfig | PermissionActionConfig | undefined + }) export type AgentConfig = { model?: string diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index cbb9aaecc..7be58195b 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -10926,32 +10926,98 @@ "type": "string", "enum": ["ask", "allow", "deny"] }, + "PermissionObjectConfig": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "$ref": "#/components/schemas/PermissionActionConfig" + } + }, + "PermissionRuleConfig": { + "anyOf": [ + { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + { + "$ref": "#/components/schemas/PermissionObjectConfig" + } + ] + }, "PermissionConfig": { "anyOf": [ { "$ref": "#/components/schemas/PermissionActionConfig" }, { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "anyOf": [ - { - "$ref": "#/components/schemas/PermissionActionConfig" + "allOf": [ + { + "type": "object", + "propertyNames": { + "type": "string" }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/components/schemas/PermissionActionConfig" - } + "additionalProperties": { + "$ref": "#/components/schemas/PermissionRuleConfig" } - ] - } + }, + { + "type": "object", + "properties": { + "read": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "edit": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "glob": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "grep": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "list": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "bash": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "task": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "external_directory": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "todowrite": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "question": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "webfetch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "websearch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "codesearch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "lsp": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "doom_loop": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "skill": { + "$ref": "#/components/schemas/PermissionRuleConfig" + } + }, + "additionalProperties": { + "$ref": "#/components/schemas/PermissionRuleConfig" + } + } + ] } ] }, From bad732c26a8c093ce7a3d724432f05470e953ee2 Mon Sep 17 00:00:00 2001 From: opencode Date: Sat, 25 Apr 2026 14:37:01 +0000 Subject: [PATCH 154/426] sync release versions for v1.14.25 --- bun.lock | 32 +++++++++++++------------- 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-electron/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/shared/package.json | 2 +- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index d0640276b..ff5f6bb7d 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -83,7 +83,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -117,7 +117,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -144,7 +144,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -168,7 +168,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -192,7 +192,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -225,7 +225,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -269,7 +269,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -298,7 +298,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -314,7 +314,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.14.24", + "version": "1.14.25", "bin": { "opencode": "./bin/opencode", }, @@ -460,7 +460,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -495,7 +495,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "cross-spawn": "catalog:", }, @@ -510,7 +510,7 @@ }, "packages/shared": { "name": "@opencode-ai/shared", - "version": "1.14.24", + "version": "1.14.25", "bin": { "opencode": "./bin/opencode", }, @@ -534,7 +534,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -569,7 +569,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -618,7 +618,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.14.24", + "version": "1.14.25", "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 324d8058c..7f65da4d9 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.14.24", + "version": "1.14.25", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index e553d1d26..5abd19256 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.14.24", + "version": "1.14.25", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 09dad6014..31f4f9a0a 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.14.24", + "version": "1.14.25", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 2005a941b..4f30ea99b 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.14.24", + "version": "1.14.25", "$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 16d99c7c4..39556c7d3 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.14.24", + "version": "1.14.25", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 0c9d8b253..f382ad16d 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.14.24", + "version": "1.14.25", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 34a1375f6..242bd1971 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.14.24", + "version": "1.14.25", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 13be3723d..5f6b14ed7 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.14.24", + "version": "1.14.25", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index d8c9ecc8c..6deaee220 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.14.24" +version = "1.14.25" 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.14.24/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/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.14.24/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/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.14.24/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 5a1e2beb4..6c1428ad5 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.14.24", + "version": "1.14.25", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 8d54ad2e7..a0b6ddaff 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.24", + "version": "1.14.25", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 3678ccd25..88f7a65f6 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.14.24", + "version": "1.14.25", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index d924f1e68..3f6ffa7a5 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.14.24", + "version": "1.14.25", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/shared/package.json b/packages/shared/package.json index 151751bda..beb0d50ed 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.24", + "version": "1.14.25", "name": "@opencode-ai/shared", "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index 89f54026a..9ab39fad7 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.14.24", + "version": "1.14.25", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 6339b1f5b..9feb8c035 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.14.24", + "version": "1.14.25", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index a8e9d0e0e..ce4a80436 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.14.24", + "version": "1.14.25", "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 217cb037a..73c6cc9fb 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.14.24", + "version": "1.14.25", "publisher": "sst-dev", "repository": { "type": "git", From d5bfaef53d36b9b3236600a92c21a5e226de9151 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 10:42:31 -0400 Subject: [PATCH 155/426] feat(httpapi): bridge instance read endpoints (#24258) --- packages/opencode/specs/effect/http-api.md | 16 ++- packages/opencode/src/project/vcs.ts | 45 ++++---- .../routes/instance/httpapi/instance.ts | 103 ++++++++++++++++++ .../server/routes/instance/httpapi/server.ts | 2 + .../src/server/routes/instance/index.ts | 10 +- .../test/server/httpapi-instance.test.ts | 53 +++++++++ 6 files changed, 193 insertions(+), 36 deletions(-) create mode 100644 packages/opencode/src/server/routes/instance/httpapi/instance.ts create mode 100644 packages/opencode/test/server/httpapi-instance.test.ts diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index ad9fcb2ba..7f19a612b 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -139,8 +139,8 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `project` | `bridged` partial | reads only; git-init remains Hono | | `file` | `bridged` partial | list/content/status only | | `mcp` | `bridged` partial | status only | -| `workspace` | `implemented` | `HttpApi` group exists, but bridge mounting needs verification | -| top-level instance reads | `next` | path, vcs, command, agent, skill, lsp, formatter | +| `workspace` | `bridged` | list, get, enter | +| top-level instance reads | `bridged` partial | path and vcs reads; command, agent, skill, lsp, formatter next | | experimental JSON routes | `next/later` | console, tool, worktree, resource, global session list | | `session` | `later/special` | large stateful surface plus streaming | | `sync` | `later` | process/control side effects | @@ -150,11 +150,9 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Next PRs -1. Add bridge-level auth and instance-context tests for the current `HttpApi` bridge. -2. Produce a generated route inventory from Hono registrations and update `Current Route Status` with exact paths. -3. Fix the `workspace` status: mount it if it should be reachable, or remove it from the composed `HttpApi` layer. -4. Port the top-level JSON reads. -5. Start the Effect OpenAPI/SDK generation path for already-bridged routes. +1. Produce a generated route inventory from Hono registrations and update `Current Route Status` with exact paths. +2. Continue porting top-level JSON reads. +3. Start the Effect OpenAPI/SDK generation path for already-bridged routes. ## Checklist @@ -164,9 +162,9 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho - [x] Provide auth, instance lookup, and observability in the Effect route layer. - [x] Attach auth middleware in route modules. - [x] Support `auth_token` as a query security scheme. -- [ ] Add bridge-level auth and instance tests. +- [x] Add bridge-level auth and instance tests. - [ ] Complete exact Hono route inventory. -- [ ] Resolve implemented-but-unmounted route groups. +- [x] Resolve implemented-but-unmounted route groups. - [ ] Port remaining JSON routes. - [ ] Generate SDK/OpenAPI from Effect routes. - [ ] Flip ported JSON routes to default-on with fallback. diff --git a/packages/opencode/src/project/vcs.ts b/packages/opencode/src/project/vcs.ts index e8c6ff2ac..1c1da97bf 100644 --- a/packages/opencode/src/project/vcs.ts +++ b/packages/opencode/src/project/vcs.ts @@ -8,7 +8,8 @@ import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { FileWatcher } from "@/file/watcher" import { Git } from "@/git" import { Log } from "@/util" -import z from "zod" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" const log = Log.create({ service: "vcs" }) @@ -101,8 +102,8 @@ const compare = Effect.fnUntraced(function* ( ) }) -export const Mode = z.enum(["git", "branch"]) -export type Mode = z.infer +export const Mode = Schema.Literals(["git", "branch"]).pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Mode = Schema.Schema.Type export const Event = { BranchUpdated: BusEvent.define( @@ -113,28 +114,24 @@ export const Event = { ), } -export const Info = z - .object({ - branch: z.string().optional(), - default_branch: z.string().optional(), - }) - .meta({ - ref: "VcsInfo", - }) -export type Info = z.infer +export const Info = Schema.Struct({ + branch: Schema.optional(Schema.String), + default_branch: Schema.optional(Schema.String), +}) + .annotate({ identifier: "VcsInfo" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Schema.Schema.Type -export const FileDiff = z - .object({ - file: z.string(), - patch: z.string(), - additions: z.number(), - deletions: z.number(), - status: z.enum(["added", "deleted", "modified"]).optional(), - }) - .meta({ - ref: "VcsFileDiff", - }) -export type FileDiff = z.infer +export const FileDiff = Schema.Struct({ + file: Schema.String, + patch: Schema.String, + additions: Schema.Number, + deletions: Schema.Number, + status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])), +}) + .annotate({ identifier: "VcsFileDiff" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type FileDiff = Schema.Schema.Type export interface Interface { readonly init: () => Effect.Effect diff --git a/packages/opencode/src/server/routes/instance/httpapi/instance.ts b/packages/opencode/src/server/routes/instance/httpapi/instance.ts new file mode 100644 index 000000000..f7c3a02ad --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/instance.ts @@ -0,0 +1,103 @@ +import { Global } from "@/global" +import { Vcs } from "@/project" +import * as InstanceState from "@/effect/instance-state" +import { Effect, Layer, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" + +const PathInfo = Schema.Struct({ + home: Schema.String, + state: Schema.String, + config: Schema.String, + worktree: Schema.String, + directory: Schema.String, +}).annotate({ identifier: "Path" }) + +const VcsDiffQuery = Schema.Struct({ + mode: Vcs.Mode, +}) + +export const InstancePaths = { + path: "/path", + vcs: "/vcs", + vcsDiff: "/vcs/diff", +} as const + +export const InstanceApi = HttpApi.make("instance") + .add( + HttpApiGroup.make("instance") + .add( + HttpApiEndpoint.get("path", InstancePaths.path, { + success: PathInfo, + }).annotateMerge( + OpenApi.annotations({ + identifier: "path.get", + summary: "Get paths", + description: "Retrieve the current working directory and related path information for the OpenCode instance.", + }), + ), + HttpApiEndpoint.get("vcs", InstancePaths.vcs, { + success: Vcs.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "vcs.get", + summary: "Get VCS info", + description: "Retrieve version control system (VCS) information for the current project, such as git branch.", + }), + ), + HttpApiEndpoint.get("vcsDiff", InstancePaths.vcsDiff, { + query: VcsDiffQuery, + success: Schema.Array(Vcs.FileDiff), + }).annotateMerge( + OpenApi.annotations({ + identifier: "vcs.diff", + summary: "Get VCS diff", + description: "Retrieve the current git diff for the working tree or against the default branch.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "instance", + description: "Experimental HttpApi instance read routes.", + }), + ) + .middleware(Authorization), + ) + .annotateMerge( + OpenApi.annotations({ + title: "opencode experimental HttpApi", + version: "0.0.1", + description: "Experimental HttpApi surface for selected instance routes.", + }), + ) + +export const instanceHandlers = Layer.unwrap( + Effect.gen(function* () { + const vcs = yield* Vcs.Service + + const getPath = Effect.fn("InstanceHttpApi.path")(function* () { + const ctx = yield* InstanceState.context + return { + home: Global.Path.home, + state: Global.Path.state, + config: Global.Path.config, + worktree: ctx.worktree, + directory: ctx.directory, + } + }) + + const getVcs = Effect.fn("InstanceHttpApi.vcs")(function* () { + const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], { concurrency: 2 }) + return { branch, default_branch } + }) + + const getVcsDiff = Effect.fn("InstanceHttpApi.vcsDiff")(function* (ctx: { query: { mode: Vcs.Mode } }) { + return yield* vcs.diff(ctx.query.mode) + }) + + return HttpApiBuilder.group(InstanceApi, "instance", (handlers) => + handlers.handle("path", getPath).handle("vcs", getVcs).handle("vcsDiff", getVcsDiff), + ) + }), +).pipe(Layer.provide(Vcs.defaultLayer)) diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index 14c2550ed..903cd103b 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -11,6 +11,7 @@ import { Filesystem } from "@/util" import { authorizationLayer } from "./auth" import { ConfigApi, configHandlers } from "./config" import { FileApi, fileHandlers } from "./file" +import { InstanceApi, instanceHandlers } from "./instance" import { McpApi, mcpHandlers } from "./mcp" import { PermissionApi, permissionHandlers } from "./permission" import { ProjectApi, projectHandlers } from "./project" @@ -63,6 +64,7 @@ const instance = HttpRouter.middleware()( export const routes = Layer.mergeAll( HttpApiBuilder.layer(ConfigApi).pipe(Layer.provide(configHandlers)), HttpApiBuilder.layer(FileApi).pipe(Layer.provide(fileHandlers)), + HttpApiBuilder.layer(InstanceApi).pipe(Layer.provide(instanceHandlers)), HttpApiBuilder.layer(McpApi).pipe(Layer.provide(mcpHandlers)), HttpApiBuilder.layer(ProjectApi).pipe(Layer.provide(projectHandlers)), HttpApiBuilder.layer(QuestionApi).pipe(Layer.provide(questionHandlers)), diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index b899eb108..bc9d2b2ad 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -17,6 +17,7 @@ import { PermissionRoutes } from "./permission" import { Flag } from "@/flag/flag" import { ExperimentalHttpApiServer } from "./httpapi/server" import { FilePaths } from "./httpapi/file" +import { InstancePaths } from "./httpapi/instance" import { McpPaths } from "./httpapi/mcp" import { ProjectRoutes } from "./project" import { SessionRoutes } from "./session" @@ -53,6 +54,9 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get(FilePaths.list, (c) => handler(c.req.raw, context)) app.get(FilePaths.content, (c) => handler(c.req.raw, context)) app.get(FilePaths.status, (c) => handler(c.req.raw, context)) + app.get(InstancePaths.path, (c) => handler(c.req.raw, context)) + app.get(InstancePaths.vcs, (c) => handler(c.req.raw, context)) + app.get(InstancePaths.vcsDiff, (c) => handler(c.req.raw, context)) app.get(McpPaths.status, (c) => handler(c.req.raw, context)) } @@ -142,7 +146,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { description: "VCS info", content: { "application/json": { - schema: resolver(Vcs.Info), + schema: resolver(Vcs.Info.zod), }, }, }, @@ -168,7 +172,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { description: "VCS diff", content: { "application/json": { - schema: resolver(Vcs.FileDiff.array()), + schema: resolver(Vcs.FileDiff.zod.array()), }, }, }, @@ -177,7 +181,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { validator( "query", z.object({ - mode: Vcs.Mode, + mode: Vcs.Mode.zod, }), ), async (c) => diff --git a/packages/opencode/test/server/httpapi-instance.test.ts b/packages/opencode/test/server/httpapi-instance.test.ts new file mode 100644 index 000000000..f25d29518 --- /dev/null +++ b/packages/opencode/test/server/httpapi-instance.test.ts @@ -0,0 +1,53 @@ +import { afterEach, describe, expect, test } from "bun:test" +import type { UpgradeWebSocket } from "hono/ws" +import path from "path" +import { Flag } from "../../src/flag/flag" +import { Instance } from "../../src/project/instance" +import { InstanceRoutes } from "../../src/server/routes/instance" +import { InstancePaths } from "../../src/server/routes/instance/httpapi/instance" +import { Log } from "../../src/util" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" + +void Log.init({ print: false }) + +const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI +const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket + +function app() { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true + return InstanceRoutes(websocket) +} + +afterEach(async () => { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original + await Instance.disposeAll() + await resetDatabase() +}) + +describe("instance HttpApi", () => { + test("serves path and VCS read endpoints through Hono bridge", async () => { + await using tmp = await tmpdir({ git: true }) + await Bun.write(path.join(tmp.path, "changed.txt"), "hello") + + const vcsDiff = new URL(`http://localhost${InstancePaths.vcsDiff}`) + vcsDiff.searchParams.set("mode", "git") + + const [paths, vcs, diff] = await Promise.all([ + app().request(InstancePaths.path, { headers: { "x-opencode-directory": tmp.path } }), + app().request(InstancePaths.vcs, { headers: { "x-opencode-directory": tmp.path } }), + app().request(vcsDiff, { headers: { "x-opencode-directory": tmp.path } }), + ]) + + expect(paths.status).toBe(200) + expect(await paths.json()).toMatchObject({ directory: tmp.path, worktree: tmp.path }) + + expect(vcs.status).toBe(200) + expect(await vcs.json()).toMatchObject({ branch: expect.any(String) }) + + expect(diff.status).toBe(200) + expect(await diff.json()).toContainEqual( + expect.objectContaining({ file: "changed.txt", additions: 1, status: "added" }), + ) + }) +}) From 5b0e828c10a2d33cb5284566a705a9d61f98b8f1 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 14:43:27 +0000 Subject: [PATCH 156/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 4 ++-- .../opencode/src/server/routes/instance/httpapi/instance.ts | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 7f19a612b..1b9da7a2c 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -139,8 +139,8 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `project` | `bridged` partial | reads only; git-init remains Hono | | `file` | `bridged` partial | list/content/status only | | `mcp` | `bridged` partial | status only | -| `workspace` | `bridged` | list, get, enter | -| top-level instance reads | `bridged` partial | path and vcs reads; command, agent, skill, lsp, formatter next | +| `workspace` | `bridged` | list, get, enter | +| top-level instance reads | `bridged` partial | path and vcs reads; command, agent, skill, lsp, formatter next | | experimental JSON routes | `next/later` | console, tool, worktree, resource, global session list | | `session` | `later/special` | large stateful surface plus streaming | | `sync` | `later` | process/control side effects | diff --git a/packages/opencode/src/server/routes/instance/httpapi/instance.ts b/packages/opencode/src/server/routes/instance/httpapi/instance.ts index f7c3a02ad..97b53c1e9 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/instance.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/instance.ts @@ -33,7 +33,8 @@ export const InstanceApi = HttpApi.make("instance") OpenApi.annotations({ identifier: "path.get", summary: "Get paths", - description: "Retrieve the current working directory and related path information for the OpenCode instance.", + description: + "Retrieve the current working directory and related path information for the OpenCode instance.", }), ), HttpApiEndpoint.get("vcs", InstancePaths.vcs, { @@ -42,7 +43,8 @@ export const InstanceApi = HttpApi.make("instance") OpenApi.annotations({ identifier: "vcs.get", summary: "Get VCS info", - description: "Retrieve version control system (VCS) information for the current project, such as git branch.", + description: + "Retrieve version control system (VCS) information for the current project, such as git branch.", }), ), HttpApiEndpoint.get("vcsDiff", InstancePaths.vcsDiff, { From 37aa8442dc023fad250f2573c8235a544789900c Mon Sep 17 00:00:00 2001 From: Dax Date: Sat, 25 Apr 2026 10:46:16 -0400 Subject: [PATCH 157/426] refactor: remove lazy cross-spawn runtime (#24305) --- packages/opencode/src/effect/cross-spawn-spawner.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/opencode/src/effect/cross-spawn-spawner.ts b/packages/opencode/src/effect/cross-spawn-spawner.ts index 5e25263a0..ad8d4126d 100644 --- a/packages/opencode/src/effect/cross-spawn-spawner.ts +++ b/packages/opencode/src/effect/cross-spawn-spawner.ts @@ -502,13 +502,4 @@ export const layer: Layer.Layer { - // Dynamic import to avoid circular dep: cross-spawn-spawner → run-service → Instance → project → cross-spawn-spawner - const { makeRuntime } = await import("@/effect/run-service") - return makeRuntime(ChildProcessSpawner, defaultLayer) -}) - -type RT = Awaited> -export const runPromiseExit: RT["runPromiseExit"] = async (...args) => (await rt()).runPromiseExit(...(args as [any])) +export * as CrossSpawnSpawner from "./cross-spawn-spawner" From 62ef2a220723a6d6cb050e523fcdfaa974dafdda Mon Sep 17 00:00:00 2001 From: Dax Date: Sat, 25 Apr 2026 10:59:17 -0400 Subject: [PATCH 158/426] refactor: rename shared package to core (#24309) --- bun.lock | 60 +++++++++---------- packages/app/package.json | 2 +- .../src/components/dialog-edit-project.tsx | 2 +- packages/app/src/components/dialog-fork.tsx | 2 +- .../components/dialog-select-directory.tsx | 2 +- .../app/src/components/dialog-select-file.tsx | 4 +- .../prompt-input/build-request-parts.ts | 2 +- .../components/prompt-input/context-items.tsx | 2 +- .../components/prompt-input/slash-popover.tsx | 2 +- .../components/prompt-input/submit.test.ts | 2 +- .../app/src/components/prompt-input/submit.ts | 4 +- .../session/session-context-tab.tsx | 4 +- .../src/components/session/session-header.tsx | 2 +- .../components/session/session-new-view.tsx | 2 +- .../session/session-sortable-tab.tsx | 2 +- packages/app/src/context/file.tsx | 2 +- packages/app/src/context/global-sync.tsx | 2 +- .../app/src/context/global-sync/bootstrap.ts | 4 +- .../src/context/global-sync/event-reducer.ts | 2 +- packages/app/src/context/local.tsx | 2 +- packages/app/src/context/notification.tsx | 4 +- .../context/permission-auto-respond.test.ts | 2 +- .../src/context/permission-auto-respond.ts | 2 +- packages/app/src/context/prompt.tsx | 2 +- packages/app/src/context/sync.tsx | 4 +- packages/app/src/pages/directory-layout.tsx | 2 +- packages/app/src/pages/home.tsx | 2 +- packages/app/src/pages/layout.tsx | 8 +-- packages/app/src/pages/layout/helpers.ts | 2 +- .../app/src/pages/layout/sidebar-items.tsx | 2 +- .../app/src/pages/layout/sidebar-project.tsx | 2 +- .../src/pages/layout/sidebar-workspace.tsx | 4 +- packages/app/src/pages/session.tsx | 2 +- packages/app/src/pages/session/file-tabs.tsx | 2 +- .../src/pages/session/message-timeline.tsx | 4 +- .../pages/session/use-session-commands.tsx | 2 +- packages/app/src/utils/base64.ts | 2 +- packages/app/src/utils/persist.ts | 2 +- packages/{shared => core}/package.json | 2 +- packages/{shared => core}/src/filesystem.ts | 0 packages/{shared => core}/src/global.ts | 0 packages/{shared => core}/src/types.d.ts | 0 packages/{shared => core}/src/util/array.ts | 0 packages/{shared => core}/src/util/binary.ts | 0 .../{shared => core}/src/util/effect-flock.ts | 0 packages/{shared => core}/src/util/encode.ts | 0 packages/{shared => core}/src/util/error.ts | 0 packages/{shared => core}/src/util/flock.ts | 0 packages/{shared => core}/src/util/fn.ts | 0 packages/{shared => core}/src/util/glob.ts | 0 packages/{shared => core}/src/util/hash.ts | 0 .../{shared => core}/src/util/identifier.ts | 0 packages/{shared => core}/src/util/iife.ts | 0 packages/{shared => core}/src/util/lazy.ts | 0 packages/{shared => core}/src/util/module.ts | 0 packages/{shared => core}/src/util/path.ts | 0 packages/{shared => core}/src/util/retry.ts | 0 packages/{shared => core}/src/util/slug.ts | 0 packages/{shared => core}/sst-env.d.ts | 0 .../test/filesystem/filesystem.test.ts | 2 +- .../test/fixture/effect-flock-worker.ts | 6 +- .../test/fixture/flock-worker.ts | 2 +- packages/{shared => core}/test/lib/effect.ts | 0 .../test/util/effect-flock.test.ts | 8 +-- .../{shared => core}/test/util/flock.test.ts | 4 +- packages/{shared => core}/tsconfig.json | 0 packages/enterprise/package.json | 2 +- packages/enterprise/src/core/share.ts | 4 +- packages/enterprise/src/core/storage.ts | 2 +- .../enterprise/src/routes/share/[shareID].tsx | 6 +- packages/enterprise/test/core/share.test.ts | 2 +- packages/opencode/package.json | 2 +- packages/opencode/src/acp/agent.ts | 2 +- packages/opencode/src/auth/index.ts | 2 +- .../opencode/src/cli/cmd/tui/config/tui.ts | 2 +- .../opencode/src/cli/cmd/tui/context/kv.tsx | 2 +- .../opencode/src/cli/cmd/tui/context/sync.tsx | 2 +- .../src/cli/cmd/tui/context/theme.tsx | 2 +- .../src/cli/cmd/tui/plugin/runtime.ts | 2 +- packages/opencode/src/cli/error.ts | 2 +- packages/opencode/src/cli/ui.ts | 2 +- packages/opencode/src/config/agent.ts | 4 +- packages/opencode/src/config/command.ts | 4 +- packages/opencode/src/config/config.ts | 6 +- packages/opencode/src/config/error.ts | 2 +- packages/opencode/src/config/markdown.ts | 2 +- packages/opencode/src/config/paths.ts | 2 +- packages/opencode/src/config/plugin.ts | 2 +- .../opencode/src/control-plane/workspace.ts | 2 +- packages/opencode/src/effect/app-runtime.ts | 2 +- packages/opencode/src/file/ignore.ts | 2 +- packages/opencode/src/file/index.ts | 2 +- packages/opencode/src/file/ripgrep.ts | 2 +- packages/opencode/src/global/index.ts | 2 +- packages/opencode/src/ide/index.ts | 2 +- packages/opencode/src/index.ts | 2 +- packages/opencode/src/lsp/client.ts | 2 +- packages/opencode/src/lsp/lsp.ts | 2 +- packages/opencode/src/lsp/server.ts | 2 +- packages/opencode/src/mcp/auth.ts | 2 +- packages/opencode/src/mcp/index.ts | 4 +- packages/opencode/src/npm/index.ts | 6 +- packages/opencode/src/plugin/index.ts | 2 +- packages/opencode/src/plugin/install.ts | 2 +- packages/opencode/src/plugin/meta.ts | 2 +- packages/opencode/src/project/instance.ts | 2 +- packages/opencode/src/project/project.ts | 2 +- packages/opencode/src/project/vcs.ts | 2 +- packages/opencode/src/provider/models.ts | 4 +- packages/opencode/src/provider/provider.ts | 4 +- packages/opencode/src/pty/index.ts | 2 +- packages/opencode/src/server/middleware.ts | 2 +- .../src/server/routes/instance/middleware.ts | 2 +- .../src/server/routes/instance/session.ts | 2 +- packages/opencode/src/session/instruction.ts | 2 +- packages/opencode/src/session/message-v2.ts | 2 +- packages/opencode/src/session/prompt.ts | 4 +- packages/opencode/src/session/retry.ts | 2 +- packages/opencode/src/session/session.ts | 2 +- packages/opencode/src/skill/discovery.ts | 2 +- packages/opencode/src/skill/index.ts | 6 +- packages/opencode/src/snapshot/index.ts | 4 +- packages/opencode/src/storage/db.ts | 2 +- .../opencode/src/storage/json-migration.ts | 2 +- packages/opencode/src/storage/storage.ts | 4 +- packages/opencode/src/tool/apply_patch.ts | 2 +- packages/opencode/src/tool/bash.ts | 2 +- packages/opencode/src/tool/edit.ts | 2 +- .../opencode/src/tool/external-directory.ts | 2 +- packages/opencode/src/tool/glob.ts | 2 +- packages/opencode/src/tool/grep.ts | 2 +- packages/opencode/src/tool/lsp.ts | 2 +- packages/opencode/src/tool/read.ts | 2 +- packages/opencode/src/tool/registry.ts | 4 +- packages/opencode/src/tool/truncate.ts | 2 +- packages/opencode/src/tool/write.ts | 2 +- packages/opencode/src/util/bom.ts | 2 +- packages/opencode/src/util/filesystem.ts | 2 +- packages/opencode/src/util/log.ts | 2 +- packages/opencode/src/worktree/index.ts | 6 +- packages/opencode/test/config/config.test.ts | 6 +- .../test/filesystem/filesystem.test.ts | 2 +- .../opencode/test/fixture/flock-worker.ts | 2 +- packages/opencode/test/npm.test.ts | 6 +- .../opencode/test/project/project.test.ts | 2 +- packages/opencode/test/session/prompt.test.ts | 4 +- packages/opencode/test/session/retry.test.ts | 2 +- .../test/session/snapshot-tool-race.test.ts | 2 +- .../opencode/test/storage/storage.test.ts | 2 +- .../opencode/test/tool/apply_patch.test.ts | 2 +- packages/opencode/test/tool/bash.test.ts | 2 +- packages/opencode/test/tool/edit.test.ts | 2 +- packages/opencode/test/tool/glob.test.ts | 2 +- packages/opencode/test/tool/grep.test.ts | 2 +- packages/opencode/test/tool/lsp.test.ts | 2 +- packages/opencode/test/tool/read.test.ts | 2 +- packages/opencode/test/tool/write.test.ts | 2 +- packages/opencode/test/util/glob.test.ts | 2 +- packages/opencode/test/util/module.test.ts | 2 +- packages/ui/package.json | 2 +- packages/ui/src/components/file.tsx | 2 +- packages/ui/src/components/line-comment.tsx | 2 +- packages/ui/src/components/markdown.tsx | 2 +- packages/ui/src/components/message-part.tsx | 4 +- packages/ui/src/components/session-review.tsx | 4 +- packages/ui/src/components/session-turn.tsx | 4 +- 166 files changed, 218 insertions(+), 218 deletions(-) rename packages/{shared => core}/package.json (96%) rename packages/{shared => core}/src/filesystem.ts (100%) rename packages/{shared => core}/src/global.ts (100%) rename packages/{shared => core}/src/types.d.ts (100%) rename packages/{shared => core}/src/util/array.ts (100%) rename packages/{shared => core}/src/util/binary.ts (100%) rename packages/{shared => core}/src/util/effect-flock.ts (100%) rename packages/{shared => core}/src/util/encode.ts (100%) rename packages/{shared => core}/src/util/error.ts (100%) rename packages/{shared => core}/src/util/flock.ts (100%) rename packages/{shared => core}/src/util/fn.ts (100%) rename packages/{shared => core}/src/util/glob.ts (100%) rename packages/{shared => core}/src/util/hash.ts (100%) rename packages/{shared => core}/src/util/identifier.ts (100%) rename packages/{shared => core}/src/util/iife.ts (100%) rename packages/{shared => core}/src/util/lazy.ts (100%) rename packages/{shared => core}/src/util/module.ts (100%) rename packages/{shared => core}/src/util/path.ts (100%) rename packages/{shared => core}/src/util/retry.ts (100%) rename packages/{shared => core}/src/util/slug.ts (100%) rename packages/{shared => core}/sst-env.d.ts (100%) rename packages/{shared => core}/test/filesystem/filesystem.test.ts (99%) rename packages/{shared => core}/test/fixture/effect-flock-worker.ts (88%) rename packages/{shared => core}/test/fixture/flock-worker.ts (96%) rename packages/{shared => core}/test/lib/effect.ts (100%) rename packages/{shared => core}/test/util/effect-flock.test.ts (98%) rename packages/{shared => core}/test/util/flock.test.ts (98%) rename packages/{shared => core}/tsconfig.json (100%) diff --git a/bun.lock b/bun.lock index ff5f6bb7d..e28376682 100644 --- a/bun.lock +++ b/bun.lock @@ -32,8 +32,8 @@ "version": "1.14.25", "dependencies": { "@kobalte/core": "catalog:", + "@opencode-ai/core": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", "@shikijs/transformers": "3.9.2", "@solid-primitives/active-element": "2.1.3", @@ -190,6 +190,30 @@ "cloudflare": "5.2.0", }, }, + "packages/core": { + "name": "@opencode-ai/core", + "version": "1.14.25", + "bin": { + "opencode": "./bin/opencode", + }, + "dependencies": { + "@effect/platform-node": "catalog:", + "@npmcli/arborist": "catalog:", + "effect": "catalog:", + "glob": "13.0.5", + "mime-types": "3.0.2", + "minimatch": "10.2.5", + "semver": "catalog:", + "xdg-basedir": "5.1.0", + "zod": "catalog:", + }, + "devDependencies": { + "@tsconfig/bun": "catalog:", + "@types/bun": "catalog:", + "@types/npmcli__arborist": "6.3.3", + "@types/semver": "catalog:", + }, + }, "packages/desktop": { "name": "@opencode-ai/desktop", "version": "1.14.25", @@ -271,7 +295,7 @@ "name": "@opencode-ai/enterprise", "version": "1.14.25", "dependencies": { - "@opencode-ai/shared": "workspace:*", + "@opencode-ai/core": "workspace:*", "@opencode-ai/ui": "workspace:*", "@pierre/diffs": "catalog:", "@solidjs/meta": "catalog:", @@ -426,8 +450,8 @@ "@babel/core": "7.28.4", "@effect/language-service": "0.84.2", "@octokit/webhooks-types": "7.6.1", + "@opencode-ai/core": "workspace:*", "@opencode-ai/script": "workspace:*", - "@opencode-ai/shared": "workspace:*", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", @@ -508,30 +532,6 @@ "typescript": "catalog:", }, }, - "packages/shared": { - "name": "@opencode-ai/shared", - "version": "1.14.25", - "bin": { - "opencode": "./bin/opencode", - }, - "dependencies": { - "@effect/platform-node": "catalog:", - "@npmcli/arborist": "catalog:", - "effect": "catalog:", - "glob": "13.0.5", - "mime-types": "3.0.2", - "minimatch": "10.2.5", - "semver": "catalog:", - "xdg-basedir": "5.1.0", - "zod": "catalog:", - }, - "devDependencies": { - "@tsconfig/bun": "catalog:", - "@types/bun": "catalog:", - "@types/npmcli__arborist": "6.3.3", - "@types/semver": "catalog:", - }, - }, "packages/slack": { "name": "@opencode-ai/slack", "version": "1.14.25", @@ -572,8 +572,8 @@ "version": "1.14.25", "dependencies": { "@kobalte/core": "catalog:", + "@opencode-ai/core": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@opencode-ai/shared": "workspace:*", "@pierre/diffs": "catalog:", "@shikijs/transformers": "3.9.2", "@solid-primitives/bounds": "0.1.3", @@ -1554,6 +1554,8 @@ "@opencode-ai/console-resource": ["@opencode-ai/console-resource@workspace:packages/console/resource"], + "@opencode-ai/core": ["@opencode-ai/core@workspace:packages/core"], + "@opencode-ai/desktop": ["@opencode-ai/desktop@workspace:packages/desktop"], "@opencode-ai/desktop-electron": ["@opencode-ai/desktop-electron@workspace:packages/desktop-electron"], @@ -1568,8 +1570,6 @@ "@opencode-ai/sdk": ["@opencode-ai/sdk@workspace:packages/sdk/js"], - "@opencode-ai/shared": ["@opencode-ai/shared@workspace:packages/shared"], - "@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"], "@opencode-ai/storybook": ["@opencode-ai/storybook@workspace:packages/storybook"], diff --git a/packages/app/package.json b/packages/app/package.json index 7f65da4d9..f9d8150ba 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -42,7 +42,7 @@ "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/ui": "workspace:*", - "@opencode-ai/shared": "workspace:*", + "@opencode-ai/core": "workspace:*", "@shikijs/transformers": "3.9.2", "@solid-primitives/active-element": "2.1.3", "@solid-primitives/audio": "1.4.2", diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx index 8eb12daf5..b4b69246c 100644 --- a/packages/app/src/components/dialog-edit-project.tsx +++ b/packages/app/src/components/dialog-edit-project.tsx @@ -9,7 +9,7 @@ import { createStore } from "solid-js/store" import { useGlobalSDK } from "@/context/global-sdk" import { useGlobalSync } from "@/context/global-sync" import { type LocalProject, getAvatarColors } from "@/context/layout" -import { getFilename } from "@opencode-ai/shared/util/path" +import { getFilename } from "@opencode-ai/core/util/path" import { Avatar } from "@opencode-ai/ui/avatar" import { useLanguage } from "@/context/language" import { getProjectAvatarSource } from "@/pages/layout/sidebar-items" diff --git a/packages/app/src/components/dialog-fork.tsx b/packages/app/src/components/dialog-fork.tsx index 710618c30..3618a0581 100644 --- a/packages/app/src/components/dialog-fork.tsx +++ b/packages/app/src/components/dialog-fork.tsx @@ -9,7 +9,7 @@ import { List } from "@opencode-ai/ui/list" import { showToast } from "@opencode-ai/ui/toast" import { extractPromptFromParts } from "@/utils/prompt" import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client" -import { base64Encode } from "@opencode-ai/shared/util/encode" +import { base64Encode } from "@opencode-ai/core/util/encode" import { useLanguage } from "@/context/language" interface ForkableMessage { diff --git a/packages/app/src/components/dialog-select-directory.tsx b/packages/app/src/components/dialog-select-directory.tsx index 903cb1915..005d28709 100644 --- a/packages/app/src/components/dialog-select-directory.tsx +++ b/packages/app/src/components/dialog-select-directory.tsx @@ -3,7 +3,7 @@ import { Dialog } from "@opencode-ai/ui/dialog" import { FileIcon } from "@opencode-ai/ui/file-icon" import { List } from "@opencode-ai/ui/list" import type { ListRef } from "@opencode-ai/ui/list" -import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" +import { getDirectory, getFilename } from "@opencode-ai/core/util/path" import fuzzysort from "fuzzysort" import { createMemo, createResource, createSignal } from "solid-js" import { useGlobalSDK } from "@/context/global-sdk" diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index 186906f92..63a321e46 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -4,8 +4,8 @@ import { FileIcon } from "@opencode-ai/ui/file-icon" import { Icon } from "@opencode-ai/ui/icon" import { Keybind } from "@opencode-ai/ui/keybind" import { List } from "@opencode-ai/ui/list" -import { base64Encode } from "@opencode-ai/shared/util/encode" -import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" +import { base64Encode } from "@opencode-ai/core/util/encode" +import { getDirectory, getFilename } from "@opencode-ai/core/util/path" import { useNavigate } from "@solidjs/router" import { createMemo, createSignal, Match, onCleanup, Show, Switch } from "solid-js" import { formatKeybind, useCommand, type CommandOption } from "@/context/command" diff --git a/packages/app/src/components/prompt-input/build-request-parts.ts b/packages/app/src/components/prompt-input/build-request-parts.ts index c268af35e..98771aedd 100644 --- a/packages/app/src/components/prompt-input/build-request-parts.ts +++ b/packages/app/src/components/prompt-input/build-request-parts.ts @@ -1,4 +1,4 @@ -import { getFilename } from "@opencode-ai/shared/util/path" +import { getFilename } from "@opencode-ai/core/util/path" import { type AgentPartInput, type FilePartInput, type Part, type TextPartInput } from "@opencode-ai/sdk/v2/client" import type { FileSelection } from "@/context/file" import { encodeFilePath } from "@/context/file/path" diff --git a/packages/app/src/components/prompt-input/context-items.tsx b/packages/app/src/components/prompt-input/context-items.tsx index 9f20f1c04..95289f989 100644 --- a/packages/app/src/components/prompt-input/context-items.tsx +++ b/packages/app/src/components/prompt-input/context-items.tsx @@ -2,7 +2,7 @@ import { Component, For, Show } from "solid-js" import { FileIcon } from "@opencode-ai/ui/file-icon" import { IconButton } from "@opencode-ai/ui/icon-button" import { Tooltip } from "@opencode-ai/ui/tooltip" -import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/shared/util/path" +import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/core/util/path" import type { ContextItem } from "@/context/prompt" type PromptContextItem = ContextItem & { key: string } diff --git a/packages/app/src/components/prompt-input/slash-popover.tsx b/packages/app/src/components/prompt-input/slash-popover.tsx index 0c8c95923..d8c4bd035 100644 --- a/packages/app/src/components/prompt-input/slash-popover.tsx +++ b/packages/app/src/components/prompt-input/slash-popover.tsx @@ -1,7 +1,7 @@ import { Component, For, Match, Show, Switch } from "solid-js" import { FileIcon } from "@opencode-ai/ui/file-icon" import { Icon } from "@opencode-ai/ui/icon" -import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" +import { getDirectory, getFilename } from "@opencode-ai/core/util/path" export type AtOption = | { type: "agent"; name: string; display: string } diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts index cf9949723..83b6212dc 100644 --- a/packages/app/src/components/prompt-input/submit.test.ts +++ b/packages/app/src/components/prompt-input/submit.test.ts @@ -74,7 +74,7 @@ beforeAll(async () => { showToast: () => 0, })) - mock.module("@opencode-ai/shared/util/encode", () => ({ + mock.module("@opencode-ai/core/util/encode", () => ({ base64Encode: (value: string) => value, })) diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index 6805f619c..05f0a3ed2 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -1,7 +1,7 @@ import type { Message, Session } from "@opencode-ai/sdk/v2/client" import { showToast } from "@opencode-ai/ui/toast" -import { base64Encode } from "@opencode-ai/shared/util/encode" -import { Binary } from "@opencode-ai/shared/util/binary" +import { base64Encode } from "@opencode-ai/core/util/encode" +import { Binary } from "@opencode-ai/core/util/binary" import { useNavigate, useParams } from "@solidjs/router" import { batch, type Accessor } from "solid-js" import type { FileSelection } from "@/context/file" diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx index abf4c9334..43741bd3f 100644 --- a/packages/app/src/components/session/session-context-tab.tsx +++ b/packages/app/src/components/session/session-context-tab.tsx @@ -1,8 +1,8 @@ import { createMemo, createEffect, on, onCleanup, For, Show } from "solid-js" import type { JSX } from "solid-js" import { useSync } from "@/context/sync" -import { checksum } from "@opencode-ai/shared/util/encode" -import { findLast } from "@opencode-ai/shared/util/array" +import { checksum } from "@opencode-ai/core/util/encode" +import { findLast } from "@opencode-ai/core/util/array" import { same } from "@/utils/same" import { Icon } from "@opencode-ai/ui/icon" import { Accordion } from "@opencode-ai/ui/accordion" diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 021e5be67..3d4f58dee 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -7,7 +7,7 @@ import { Keybind } from "@opencode-ai/ui/keybind" import { Spinner } from "@opencode-ai/ui/spinner" import { showToast } from "@opencode-ai/ui/toast" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" -import { getFilename } from "@opencode-ai/shared/util/path" +import { getFilename } from "@opencode-ai/core/util/path" import { createEffect, createMemo, createSignal, For, onMount, Show } from "solid-js" import { createStore } from "solid-js/store" import { Portal } from "solid-js/web" diff --git a/packages/app/src/components/session/session-new-view.tsx b/packages/app/src/components/session/session-new-view.tsx index d2cac28fc..36c1eb42c 100644 --- a/packages/app/src/components/session/session-new-view.tsx +++ b/packages/app/src/components/session/session-new-view.tsx @@ -5,7 +5,7 @@ import { useSDK } from "@/context/sdk" import { useLanguage } from "@/context/language" import { Icon } from "@opencode-ai/ui/icon" import { Mark } from "@opencode-ai/ui/logo" -import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" +import { getDirectory, getFilename } from "@opencode-ai/core/util/path" const MAIN_WORKTREE = "main" const CREATE_WORKTREE = "create" diff --git a/packages/app/src/components/session/session-sortable-tab.tsx b/packages/app/src/components/session/session-sortable-tab.tsx index fb2275c44..f04228ca6 100644 --- a/packages/app/src/components/session/session-sortable-tab.tsx +++ b/packages/app/src/components/session/session-sortable-tab.tsx @@ -5,7 +5,7 @@ import { FileIcon } from "@opencode-ai/ui/file-icon" import { IconButton } from "@opencode-ai/ui/icon-button" import { TooltipKeybind } from "@opencode-ai/ui/tooltip" import { Tabs } from "@opencode-ai/ui/tabs" -import { getFilename } from "@opencode-ai/shared/util/path" +import { getFilename } from "@opencode-ai/core/util/path" import { useFile } from "@/context/file" import { useLanguage } from "@/context/language" import { useCommand } from "@/context/command" diff --git a/packages/app/src/context/file.tsx b/packages/app/src/context/file.tsx index 8998731a6..0298e3416 100644 --- a/packages/app/src/context/file.tsx +++ b/packages/app/src/context/file.tsx @@ -3,7 +3,7 @@ import { createStore, produce, reconcile } from "solid-js/store" import { createSimpleContext } from "@opencode-ai/ui/context" import { showToast } from "@opencode-ai/ui/toast" import { useParams } from "@solidjs/router" -import { getFilename } from "@opencode-ai/shared/util/path" +import { getFilename } from "@opencode-ai/core/util/path" import { useSDK } from "./sdk" import { useSync } from "./sync" import { useLanguage } from "@/context/language" diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index b742667d7..86496bad5 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -8,7 +8,7 @@ import type { Todo, } from "@opencode-ai/sdk/v2/client" import { showToast } from "@opencode-ai/ui/toast" -import { getFilename } from "@opencode-ai/shared/util/path" +import { getFilename } from "@opencode-ai/core/util/path" import { batch, createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js" import { createStore, produce, reconcile } from "solid-js/store" import { useLanguage } from "@/context/language" diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index be789a5e5..66f4a3b15 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -11,8 +11,8 @@ import type { Todo, } from "@opencode-ai/sdk/v2/client" import { showToast } from "@opencode-ai/ui/toast" -import { getFilename } from "@opencode-ai/shared/util/path" -import { retry } from "@opencode-ai/shared/util/retry" +import { getFilename } from "@opencode-ai/core/util/path" +import { retry } from "@opencode-ai/core/util/retry" import { batch } from "solid-js" import { reconcile, type SetStoreFunction, type Store } from "solid-js/store" import type { State, VcsCache } from "./types" diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts index 82408fdfe..5f43c341b 100644 --- a/packages/app/src/context/global-sync/event-reducer.ts +++ b/packages/app/src/context/global-sync/event-reducer.ts @@ -1,4 +1,4 @@ -import { Binary } from "@opencode-ai/shared/util/binary" +import { Binary } from "@opencode-ai/core/util/binary" import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store" import type { Message, diff --git a/packages/app/src/context/local.tsx b/packages/app/src/context/local.tsx index 0b0972ee6..2db0f9b04 100644 --- a/packages/app/src/context/local.tsx +++ b/packages/app/src/context/local.tsx @@ -1,5 +1,5 @@ import { createSimpleContext } from "@opencode-ai/ui/context" -import { base64Encode } from "@opencode-ai/shared/util/encode" +import { base64Encode } from "@opencode-ai/core/util/encode" import { useParams } from "@solidjs/router" import { batch, createEffect, createMemo } from "solid-js" import { createStore } from "solid-js/store" diff --git a/packages/app/src/context/notification.tsx b/packages/app/src/context/notification.tsx index 251b67b06..c926dc1d9 100644 --- a/packages/app/src/context/notification.tsx +++ b/packages/app/src/context/notification.tsx @@ -7,8 +7,8 @@ import { useGlobalSync } from "./global-sync" import { usePlatform } from "@/context/platform" import { useLanguage } from "@/context/language" import { useSettings } from "@/context/settings" -import { Binary } from "@opencode-ai/shared/util/binary" -import { base64Encode } from "@opencode-ai/shared/util/encode" +import { Binary } from "@opencode-ai/core/util/binary" +import { base64Encode } from "@opencode-ai/core/util/encode" import { decode64 } from "@/utils/base64" import { EventSessionError } from "@opencode-ai/sdk/v2" import { Persist, persisted } from "@/utils/persist" diff --git a/packages/app/src/context/permission-auto-respond.test.ts b/packages/app/src/context/permission-auto-respond.test.ts index 2f8ca6265..002ae94e5 100644 --- a/packages/app/src/context/permission-auto-respond.test.ts +++ b/packages/app/src/context/permission-auto-respond.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" import type { PermissionRequest, Session } from "@opencode-ai/sdk/v2/client" -import { base64Encode } from "@opencode-ai/shared/util/encode" +import { base64Encode } from "@opencode-ai/core/util/encode" import { autoRespondsPermission, isDirectoryAutoAccepting } from "./permission-auto-respond" const session = (input: { id: string; parentID?: string }) => diff --git a/packages/app/src/context/permission-auto-respond.ts b/packages/app/src/context/permission-auto-respond.ts index 2ebca3434..58ab75c57 100644 --- a/packages/app/src/context/permission-auto-respond.ts +++ b/packages/app/src/context/permission-auto-respond.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/shared/util/encode" +import { base64Encode } from "@opencode-ai/core/util/encode" export function acceptKey(sessionID: string, directory?: string) { if (!directory) return sessionID diff --git a/packages/app/src/context/prompt.tsx b/packages/app/src/context/prompt.tsx index 15af57b35..dffb79831 100644 --- a/packages/app/src/context/prompt.tsx +++ b/packages/app/src/context/prompt.tsx @@ -1,5 +1,5 @@ import { createSimpleContext } from "@opencode-ai/ui/context" -import { checksum } from "@opencode-ai/shared/util/encode" +import { checksum } from "@opencode-ai/core/util/encode" import { useParams } from "@solidjs/router" import { batch, createMemo, createRoot, getOwner, onCleanup } from "solid-js" import { createStore, type SetStoreFunction } from "solid-js/store" diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx index 29b7fe68c..34b597b6b 100644 --- a/packages/app/src/context/sync.tsx +++ b/packages/app/src/context/sync.tsx @@ -1,7 +1,7 @@ import { batch, createMemo } from "solid-js" import { createStore, produce, reconcile } from "solid-js/store" -import { Binary } from "@opencode-ai/shared/util/binary" -import { retry } from "@opencode-ai/shared/util/retry" +import { Binary } from "@opencode-ai/core/util/binary" +import { retry } from "@opencode-ai/core/util/retry" import { createSimpleContext } from "@opencode-ai/ui/context" import { clearSessionPrefetch, diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx index 36514f56c..90ce3c1a5 100644 --- a/packages/app/src/pages/directory-layout.tsx +++ b/packages/app/src/pages/directory-layout.tsx @@ -1,6 +1,6 @@ import { DataProvider } from "@opencode-ai/ui/context" import { showToast } from "@opencode-ai/ui/toast" -import { base64Encode } from "@opencode-ai/shared/util/encode" +import { base64Encode } from "@opencode-ai/core/util/encode" import { useLocation, useNavigate, useParams } from "@solidjs/router" import { createEffect, createMemo, createResource, type ParentProps, Show } from "solid-js" import { useLanguage } from "@/context/language" diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index 46cacdf62..2df69ee92 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -3,7 +3,7 @@ import { Button } from "@opencode-ai/ui/button" import { Logo } from "@opencode-ai/ui/logo" import { useLayout } from "@/context/layout" import { useNavigate } from "@solidjs/router" -import { base64Encode } from "@opencode-ai/shared/util/encode" +import { base64Encode } from "@opencode-ai/core/util/encode" import { Icon } from "@opencode-ai/ui/icon" import { usePlatform } from "@/context/platform" import { DateTime } from "luxon" diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index ac5cf104a..d9ce87a02 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -17,7 +17,7 @@ import { useLocation, useNavigate, useParams } from "@solidjs/router" import { useLayout, LocalProject } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" import { Persist, persisted } from "@/utils/persist" -import { base64Encode } from "@opencode-ai/shared/util/encode" +import { base64Encode } from "@opencode-ai/core/util/encode" import { decode64 } from "@/utils/base64" import { ResizeHandle } from "@opencode-ai/ui/resize-handle" import { Button } from "@opencode-ai/ui/button" @@ -25,7 +25,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button" import { Tooltip } from "@opencode-ai/ui/tooltip" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { Dialog } from "@opencode-ai/ui/dialog" -import { getFilename } from "@opencode-ai/shared/util/path" +import { getFilename } from "@opencode-ai/core/util/path" import { Session, type Message } from "@opencode-ai/sdk/v2/client" import { usePlatform } from "@/context/platform" import { useSettings } from "@/context/settings" @@ -48,8 +48,8 @@ import { } from "@/context/global-sync/session-prefetch" import { useNotification } from "@/context/notification" import { usePermission } from "@/context/permission" -import { Binary } from "@opencode-ai/shared/util/binary" -import { retry } from "@opencode-ai/shared/util/retry" +import { Binary } from "@opencode-ai/core/util/binary" +import { retry } from "@opencode-ai/core/util/retry" import { playSoundById } from "@/utils/sound" import { createAim } from "@/utils/aim" import { setNavigate } from "@/utils/notification-click" diff --git a/packages/app/src/pages/layout/helpers.ts b/packages/app/src/pages/layout/helpers.ts index 32b94c9cb..4bc5254d9 100644 --- a/packages/app/src/pages/layout/helpers.ts +++ b/packages/app/src/pages/layout/helpers.ts @@ -1,4 +1,4 @@ -import { getFilename } from "@opencode-ai/shared/util/path" +import { getFilename } from "@opencode-ai/core/util/path" import { type Session } from "@opencode-ai/sdk/v2/client" type SessionStore = { diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 9a9a1d7fc..d9fd4d6a8 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -4,7 +4,7 @@ import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" import { Spinner } from "@opencode-ai/ui/spinner" import { Tooltip } from "@opencode-ai/ui/tooltip" -import { getFilename } from "@opencode-ai/shared/util/path" +import { getFilename } from "@opencode-ai/core/util/path" import { A, useParams } from "@solidjs/router" import { type Accessor, createMemo, For, type JSX, Match, Show, Switch } from "solid-js" import { useGlobalSync } from "@/context/global-sync" diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx index 076e1ef88..2ba20092c 100644 --- a/packages/app/src/pages/layout/sidebar-project.tsx +++ b/packages/app/src/pages/layout/sidebar-project.tsx @@ -1,6 +1,6 @@ import { createMemo, For, Show, type Accessor, type JSX } from "solid-js" import { createStore } from "solid-js/store" -import { base64Encode } from "@opencode-ai/shared/util/encode" +import { base64Encode } from "@opencode-ai/core/util/encode" import { Button } from "@opencode-ai/ui/button" import { ContextMenu } from "@opencode-ai/ui/context-menu" import { HoverCard } from "@opencode-ai/ui/hover-card" diff --git a/packages/app/src/pages/layout/sidebar-workspace.tsx b/packages/app/src/pages/layout/sidebar-workspace.tsx index cbb570106..0a3fc7f41 100644 --- a/packages/app/src/pages/layout/sidebar-workspace.tsx +++ b/packages/app/src/pages/layout/sidebar-workspace.tsx @@ -3,8 +3,8 @@ import { createEffect, createMemo, For, Show, type Accessor, type JSX } from "so import { createStore } from "solid-js/store" import { createSortable } from "@thisbeyond/solid-dnd" import { createMediaQuery } from "@solid-primitives/media" -import { base64Encode } from "@opencode-ai/shared/util/encode" -import { getFilename } from "@opencode-ai/shared/util/path" +import { base64Encode } from "@opencode-ai/core/util/encode" +import { getFilename } from "@opencode-ai/core/util/path" import { Button } from "@opencode-ai/ui/button" import { Collapsible } from "@opencode-ai/ui/collapsible" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 4ae973b85..1345e355e 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -28,7 +28,7 @@ import { createAutoScroll } from "@opencode-ai/ui/hooks" import { previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge" import { Button } from "@opencode-ai/ui/button" import { showToast } from "@opencode-ai/ui/toast" -import { checksum } from "@opencode-ai/shared/util/encode" +import { checksum } from "@opencode-ai/core/util/encode" import { useSearchParams } from "@solidjs/router" import { NewSessionView, SessionHeader } from "@/components/session" import { useComments } from "@/context/comments" diff --git a/packages/app/src/pages/session/file-tabs.tsx b/packages/app/src/pages/session/file-tabs.tsx index 37bffcd2f..65b076d7c 100644 --- a/packages/app/src/pages/session/file-tabs.tsx +++ b/packages/app/src/pages/session/file-tabs.tsx @@ -6,7 +6,7 @@ import type { FileSearchHandle } from "@opencode-ai/ui/file" import { useFileComponent } from "@opencode-ai/ui/context/file" import { cloneSelectedLineRange, previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge" import { createLineCommentController } from "@opencode-ai/ui/line-comment-annotations" -import { sampledChecksum } from "@opencode-ai/shared/util/encode" +import { sampledChecksum } from "@opencode-ai/core/util/encode" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { IconButton } from "@opencode-ai/ui/icon-button" import { Tabs } from "@opencode-ai/ui/tabs" diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index 592ca774e..8bbaafb4e 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -15,8 +15,8 @@ import { ScrollView } from "@opencode-ai/ui/scroll-view" import { TextField } from "@opencode-ai/ui/text-field" import type { AssistantMessage, Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2" import { showToast } from "@opencode-ai/ui/toast" -import { Binary } from "@opencode-ai/shared/util/binary" -import { getFilename } from "@opencode-ai/shared/util/path" +import { Binary } from "@opencode-ai/core/util/binary" +import { getFilename } from "@opencode-ai/core/util/path" import { Popover as KobaltePopover } from "@kobalte/core/popover" import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture" import { SessionContextUsage } from "@/components/session-context-usage" diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index d649aeb0c..922299bec 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -14,7 +14,7 @@ import { useSettings } from "@/context/settings" import { useSync } from "@/context/sync" import { useTerminal } from "@/context/terminal" import { showToast } from "@opencode-ai/ui/toast" -import { findLast } from "@opencode-ai/shared/util/array" +import { findLast } from "@opencode-ai/core/util/array" import { createSessionTabs } from "@/pages/session/helpers" import { extractPromptFromParts } from "@/utils/prompt" import { UserMessage } from "@opencode-ai/sdk/v2" diff --git a/packages/app/src/utils/base64.ts b/packages/app/src/utils/base64.ts index f60dff2b6..34b904051 100644 --- a/packages/app/src/utils/base64.ts +++ b/packages/app/src/utils/base64.ts @@ -1,4 +1,4 @@ -import { base64Decode } from "@opencode-ai/shared/util/encode" +import { base64Decode } from "@opencode-ai/core/util/encode" export function decode64(value: string | undefined) { if (value === undefined) return diff --git a/packages/app/src/utils/persist.ts b/packages/app/src/utils/persist.ts index 0cac30cb1..024552727 100644 --- a/packages/app/src/utils/persist.ts +++ b/packages/app/src/utils/persist.ts @@ -1,6 +1,6 @@ import { Platform, usePlatform } from "@/context/platform" import { makePersisted, type AsyncStorage, type SyncStorage } from "@solid-primitives/storage" -import { checksum } from "@opencode-ai/shared/util/encode" +import { checksum } from "@opencode-ai/core/util/encode" import { createResource, type Accessor } from "solid-js" import type { SetStoreFunction, Store } from "solid-js/store" diff --git a/packages/shared/package.json b/packages/core/package.json similarity index 96% rename from packages/shared/package.json rename to packages/core/package.json index beb0d50ed..48d44ccf3 100644 --- a/packages/shared/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "version": "1.14.25", - "name": "@opencode-ai/shared", + "name": "@opencode-ai/core", "type": "module", "license": "MIT", "private": true, diff --git a/packages/shared/src/filesystem.ts b/packages/core/src/filesystem.ts similarity index 100% rename from packages/shared/src/filesystem.ts rename to packages/core/src/filesystem.ts diff --git a/packages/shared/src/global.ts b/packages/core/src/global.ts similarity index 100% rename from packages/shared/src/global.ts rename to packages/core/src/global.ts diff --git a/packages/shared/src/types.d.ts b/packages/core/src/types.d.ts similarity index 100% rename from packages/shared/src/types.d.ts rename to packages/core/src/types.d.ts diff --git a/packages/shared/src/util/array.ts b/packages/core/src/util/array.ts similarity index 100% rename from packages/shared/src/util/array.ts rename to packages/core/src/util/array.ts diff --git a/packages/shared/src/util/binary.ts b/packages/core/src/util/binary.ts similarity index 100% rename from packages/shared/src/util/binary.ts rename to packages/core/src/util/binary.ts diff --git a/packages/shared/src/util/effect-flock.ts b/packages/core/src/util/effect-flock.ts similarity index 100% rename from packages/shared/src/util/effect-flock.ts rename to packages/core/src/util/effect-flock.ts diff --git a/packages/shared/src/util/encode.ts b/packages/core/src/util/encode.ts similarity index 100% rename from packages/shared/src/util/encode.ts rename to packages/core/src/util/encode.ts diff --git a/packages/shared/src/util/error.ts b/packages/core/src/util/error.ts similarity index 100% rename from packages/shared/src/util/error.ts rename to packages/core/src/util/error.ts diff --git a/packages/shared/src/util/flock.ts b/packages/core/src/util/flock.ts similarity index 100% rename from packages/shared/src/util/flock.ts rename to packages/core/src/util/flock.ts diff --git a/packages/shared/src/util/fn.ts b/packages/core/src/util/fn.ts similarity index 100% rename from packages/shared/src/util/fn.ts rename to packages/core/src/util/fn.ts diff --git a/packages/shared/src/util/glob.ts b/packages/core/src/util/glob.ts similarity index 100% rename from packages/shared/src/util/glob.ts rename to packages/core/src/util/glob.ts diff --git a/packages/shared/src/util/hash.ts b/packages/core/src/util/hash.ts similarity index 100% rename from packages/shared/src/util/hash.ts rename to packages/core/src/util/hash.ts diff --git a/packages/shared/src/util/identifier.ts b/packages/core/src/util/identifier.ts similarity index 100% rename from packages/shared/src/util/identifier.ts rename to packages/core/src/util/identifier.ts diff --git a/packages/shared/src/util/iife.ts b/packages/core/src/util/iife.ts similarity index 100% rename from packages/shared/src/util/iife.ts rename to packages/core/src/util/iife.ts diff --git a/packages/shared/src/util/lazy.ts b/packages/core/src/util/lazy.ts similarity index 100% rename from packages/shared/src/util/lazy.ts rename to packages/core/src/util/lazy.ts diff --git a/packages/shared/src/util/module.ts b/packages/core/src/util/module.ts similarity index 100% rename from packages/shared/src/util/module.ts rename to packages/core/src/util/module.ts diff --git a/packages/shared/src/util/path.ts b/packages/core/src/util/path.ts similarity index 100% rename from packages/shared/src/util/path.ts rename to packages/core/src/util/path.ts diff --git a/packages/shared/src/util/retry.ts b/packages/core/src/util/retry.ts similarity index 100% rename from packages/shared/src/util/retry.ts rename to packages/core/src/util/retry.ts diff --git a/packages/shared/src/util/slug.ts b/packages/core/src/util/slug.ts similarity index 100% rename from packages/shared/src/util/slug.ts rename to packages/core/src/util/slug.ts diff --git a/packages/shared/sst-env.d.ts b/packages/core/sst-env.d.ts similarity index 100% rename from packages/shared/sst-env.d.ts rename to packages/core/sst-env.d.ts diff --git a/packages/shared/test/filesystem/filesystem.test.ts b/packages/core/test/filesystem/filesystem.test.ts similarity index 99% rename from packages/shared/test/filesystem/filesystem.test.ts rename to packages/core/test/filesystem/filesystem.test.ts index b49026bcb..b77f4e356 100644 --- a/packages/shared/test/filesystem/filesystem.test.ts +++ b/packages/core/test/filesystem/filesystem.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect } from "bun:test" import { Effect, Layer, FileSystem } from "effect" import { NodeFileSystem } from "@effect/platform-node" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { testEffect } from "../lib/effect" import path from "path" diff --git a/packages/shared/test/fixture/effect-flock-worker.ts b/packages/core/test/fixture/effect-flock-worker.ts similarity index 88% rename from packages/shared/test/fixture/effect-flock-worker.ts rename to packages/core/test/fixture/effect-flock-worker.ts index c9116c2d5..3dc3ee2c8 100644 --- a/packages/shared/test/fixture/effect-flock-worker.ts +++ b/packages/core/test/fixture/effect-flock-worker.ts @@ -1,9 +1,9 @@ import fs from "fs/promises" import os from "os" import { Effect, Layer } from "effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" -import { Global } from "@opencode-ai/shared/global" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { EffectFlock } from "@opencode-ai/core/util/effect-flock" +import { Global } from "@opencode-ai/core/global" type Msg = { key: string diff --git a/packages/shared/test/fixture/flock-worker.ts b/packages/core/test/fixture/flock-worker.ts similarity index 96% rename from packages/shared/test/fixture/flock-worker.ts rename to packages/core/test/fixture/flock-worker.ts index 9954d290c..0b9c314c0 100644 --- a/packages/shared/test/fixture/flock-worker.ts +++ b/packages/core/test/fixture/flock-worker.ts @@ -1,5 +1,5 @@ import fs from "fs/promises" -import { Flock } from "@opencode-ai/shared/util/flock" +import { Flock } from "@opencode-ai/core/util/flock" type Msg = { key: string diff --git a/packages/shared/test/lib/effect.ts b/packages/core/test/lib/effect.ts similarity index 100% rename from packages/shared/test/lib/effect.ts rename to packages/core/test/lib/effect.ts diff --git a/packages/shared/test/util/effect-flock.test.ts b/packages/core/test/util/effect-flock.test.ts similarity index 98% rename from packages/shared/test/util/effect-flock.test.ts rename to packages/core/test/util/effect-flock.test.ts index bd71e4f02..9e8bc24ac 100644 --- a/packages/shared/test/util/effect-flock.test.ts +++ b/packages/core/test/util/effect-flock.test.ts @@ -5,10 +5,10 @@ import path from "path" import os from "os" import { Cause, Effect, Exit, Layer } from "effect" import { testEffect } from "../lib/effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" -import { Global } from "@opencode-ai/shared/global" -import { Hash } from "@opencode-ai/shared/util/hash" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { EffectFlock } from "@opencode-ai/core/util/effect-flock" +import { Global } from "@opencode-ai/core/global" +import { Hash } from "@opencode-ai/core/util/hash" function lock(dir: string, key: string) { return path.join(dir, Hash.fast(key) + ".lock") diff --git a/packages/shared/test/util/flock.test.ts b/packages/core/test/util/flock.test.ts similarity index 98% rename from packages/shared/test/util/flock.test.ts rename to packages/core/test/util/flock.test.ts index f1053dfd2..e1b647b64 100644 --- a/packages/shared/test/util/flock.test.ts +++ b/packages/core/test/util/flock.test.ts @@ -3,8 +3,8 @@ import fs from "fs/promises" import { spawn } from "child_process" import path from "path" import os from "os" -import { Flock } from "@opencode-ai/shared/util/flock" -import { Hash } from "@opencode-ai/shared/util/hash" +import { Flock } from "@opencode-ai/core/util/flock" +import { Hash } from "@opencode-ai/core/util/hash" type Msg = { key: string diff --git a/packages/shared/tsconfig.json b/packages/core/tsconfig.json similarity index 100% rename from packages/shared/tsconfig.json rename to packages/core/tsconfig.json diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 5f6b14ed7..a5a2997b9 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -13,7 +13,7 @@ "shell-prod": "sst shell --target Teams --stage production" }, "dependencies": { - "@opencode-ai/shared": "workspace:*", + "@opencode-ai/core": "workspace:*", "@opencode-ai/ui": "workspace:*", "aws4fetch": "^1.0.20", "@pierre/diffs": "catalog:", diff --git a/packages/enterprise/src/core/share.ts b/packages/enterprise/src/core/share.ts index 1a343272f..fb8cd3029 100644 --- a/packages/enterprise/src/core/share.ts +++ b/packages/enterprise/src/core/share.ts @@ -1,6 +1,6 @@ import { Message, Model, Part, Session, SnapshotFileDiff } from "@opencode-ai/sdk/v2" -import { fn } from "@opencode-ai/shared/util/fn" -import { iife } from "@opencode-ai/shared/util/iife" +import { fn } from "@opencode-ai/core/util/fn" +import { iife } from "@opencode-ai/core/util/iife" import z from "zod" import { Storage } from "./storage" diff --git a/packages/enterprise/src/core/storage.ts b/packages/enterprise/src/core/storage.ts index a6222e415..58d61aca7 100644 --- a/packages/enterprise/src/core/storage.ts +++ b/packages/enterprise/src/core/storage.ts @@ -1,5 +1,5 @@ import { AwsClient } from "aws4fetch" -import { lazy } from "@opencode-ai/shared/util/lazy" +import { lazy } from "@opencode-ai/core/util/lazy" export namespace Storage { export interface Adapter { diff --git a/packages/enterprise/src/routes/share/[shareID].tsx b/packages/enterprise/src/routes/share/[shareID].tsx index f3be14e39..b12afce27 100644 --- a/packages/enterprise/src/routes/share/[shareID].tsx +++ b/packages/enterprise/src/routes/share/[shareID].tsx @@ -10,9 +10,9 @@ import { Share } from "~/core/share" import { Logo, Mark } from "@opencode-ai/ui/logo" import { IconButton } from "@opencode-ai/ui/icon-button" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" -import { iife } from "@opencode-ai/shared/util/iife" -import { Binary } from "@opencode-ai/shared/util/binary" -import { NamedError } from "@opencode-ai/shared/util/error" +import { iife } from "@opencode-ai/core/util/iife" +import { Binary } from "@opencode-ai/core/util/binary" +import { NamedError } from "@opencode-ai/core/util/error" import { DateTime } from "luxon" import { createStore } from "solid-js/store" import z from "zod" diff --git a/packages/enterprise/test/core/share.test.ts b/packages/enterprise/test/core/share.test.ts index 2877f8e0e..15c5f9205 100644 --- a/packages/enterprise/test/core/share.test.ts +++ b/packages/enterprise/test/core/share.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test" import { Share } from "../../src/core/share" import { Storage } from "../../src/core/storage" -import { Identifier } from "@opencode-ai/shared/util/identifier" +import { Identifier } from "@opencode-ai/core/util/identifier" describe.concurrent("core.share", () => { test("should create a share", async () => { diff --git a/packages/opencode/package.json b/packages/opencode/package.json index a0b6ddaff..1c60e58a8 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -45,7 +45,7 @@ "@effect/language-service": "0.84.2", "@octokit/webhooks-types": "7.6.1", "@opencode-ai/script": "workspace:*", - "@opencode-ai/shared": "workspace:*", + "@opencode-ai/core": "workspace:*", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 24bcb9c2d..6ab24e26b 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -34,7 +34,7 @@ import { import { Log } from "../util" import { pathToFileURL } from "url" import { Filesystem } from "../util" -import { Hash } from "@opencode-ai/shared/util/hash" +import { Hash } from "@opencode-ai/core/util/hash" import { ACPSessionManager } from "./session" import type { ACPConfig } from "./types" import { Provider } from "../provider" diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index 5b4b5120f..00bc22329 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -2,7 +2,7 @@ import path from "path" import { Effect, Layer, Record, Result, Schema, Context } from "effect" import { zod } from "@/util/effect-zod" import { Global } from "../global" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key" diff --git a/packages/opencode/src/cli/cmd/tui/config/tui.ts b/packages/opencode/src/cli/cmd/tui/config/tui.ts index 9d5cd65bf..8dc6ab07e 100644 --- a/packages/opencode/src/cli/cmd/tui/config/tui.ts +++ b/packages/opencode/src/cli/cmd/tui/config/tui.ts @@ -10,7 +10,7 @@ import { TuiInfo } from "./tui-schema" import { Flag } from "@/flag/flag" import { isRecord } from "@/util/record" import { Global } from "@/global" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { CurrentWorkingDirectory } from "./cwd" import { ConfigPlugin } from "@/config/plugin" import { ConfigKeybinds } from "@/config/keybinds" diff --git a/packages/opencode/src/cli/cmd/tui/context/kv.tsx b/packages/opencode/src/cli/cmd/tui/context/kv.tsx index 43266315b..df8a8394c 100644 --- a/packages/opencode/src/cli/cmd/tui/context/kv.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/kv.tsx @@ -1,6 +1,6 @@ import { Global } from "@/global" import { Filesystem } from "@/util" -import { Flock } from "@opencode-ai/shared/util/flock" +import { Flock } from "@opencode-ai/core/util/flock" import { rename, rm } from "fs/promises" import { createSignal, type Setter } from "solid-js" import { createStore, unwrap } from "solid-js/store" diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 57326e3a1..d35deb0b6 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -22,7 +22,7 @@ import { createStore, produce, reconcile } from "solid-js/store" import { useProject } from "@tui/context/project" import { useEvent } from "@tui/context/event" import { useSDK } from "@tui/context/sdk" -import { Binary } from "@opencode-ai/shared/util/binary" +import { Binary } from "@opencode-ai/core/util/binary" import { createSimpleContext } from "./helper" import type { Snapshot } from "@/snapshot" import { useExit } from "./exit" diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx index 3ae1eb869..10f2dc49d 100644 --- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx @@ -2,7 +2,7 @@ import { CliRenderEvents, SyntaxStyle, RGBA, type TerminalColors } from "@opentu import path from "path" import { createEffect, createMemo, onCleanup, onMount } from "solid-js" import { createSimpleContext } from "./helper" -import { Glob } from "@opencode-ai/shared/util/glob" +import { Glob } from "@opencode-ai/core/util/glob" import aura from "./theme/aura.json" with { type: "json" } import ayu from "./theme/ayu.json" with { type: "json" } import catppuccin from "./theme/catppuccin.json" with { type: "json" } diff --git a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts index e4a0e59eb..8eda7e022 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts @@ -32,7 +32,7 @@ import { hasTheme, upsertTheme } from "../context/theme" import { Global } from "@/global" import { Filesystem } from "@/util" import { Process } from "@/util" -import { Flock } from "@opencode-ai/shared/util/flock" +import { Flock } from "@opencode-ai/core/util/flock" import { Flag } from "@/flag/flag" import { INTERNAL_TUI_PLUGINS, type InternalTuiPlugin } from "./internal" import { setupSlots, Slot as View } from "./slots" diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index f286b5166..adf52f568 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -1,4 +1,4 @@ -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { errorFormat } from "@/util/error" interface ErrorLike { diff --git a/packages/opencode/src/cli/ui.ts b/packages/opencode/src/cli/ui.ts index 46335d24a..7b4cf7f34 100644 --- a/packages/opencode/src/cli/ui.ts +++ b/packages/opencode/src/cli/ui.ts @@ -1,6 +1,6 @@ import z from "zod" import { EOL } from "os" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { logo as glyphs } from "./logo" const wordmark = [ diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index 2978916b5..a8693c8aa 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -6,8 +6,8 @@ import { Bus } from "@/bus" import { zod } from "@/util/effect-zod" import { PositiveInt } from "@/util/schema" import { Log } from "../util" -import { NamedError } from "@opencode-ai/shared/util/error" -import { Glob } from "@opencode-ai/shared/util/glob" +import { NamedError } from "@opencode-ai/core/util/error" +import { Glob } from "@opencode-ai/core/util/glob" import { configEntryNameFromPath } from "./entry-name" import { InvalidError } from "./error" import * as ConfigMarkdown from "./markdown" diff --git a/packages/opencode/src/config/command.ts b/packages/opencode/src/config/command.ts index 3e0adccc3..36cae6f97 100644 --- a/packages/opencode/src/config/command.ts +++ b/packages/opencode/src/config/command.ts @@ -2,8 +2,8 @@ export * as ConfigCommand from "./command" import { Log } from "../util" import { Schema } from "effect" -import { NamedError } from "@opencode-ai/shared/util/error" -import { Glob } from "@opencode-ai/shared/util/glob" +import { NamedError } from "@opencode-ai/core/util/error" +import { Glob } from "@opencode-ai/core/util/glob" import { Bus } from "@/bus" import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index f1ceb1b4e..3238287be 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -6,7 +6,7 @@ import z from "zod" import { mergeDeep, pipe } from "remeda" import { Global } from "../global" import fsNode from "fs/promises" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { Flag } from "../flag/flag" import { Auth } from "../auth" import { Env } from "../env" @@ -19,10 +19,10 @@ import { Event } from "../server/event" import { Account } from "@/account/account" import { isRecord } from "@/util/record" import type { ConsoleState } from "./console-state" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { InstanceState } from "@/effect" import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "effect" -import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" +import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { InstanceRef } from "@/effect/instance-ref" import { zod, ZodOverride } from "@/util/effect-zod" import { NonNegativeInt, PositiveInt, withStatics, type DeepMutable } from "@/util/schema" diff --git a/packages/opencode/src/config/error.ts b/packages/opencode/src/config/error.ts index 06f549fd8..c43598048 100644 --- a/packages/opencode/src/config/error.ts +++ b/packages/opencode/src/config/error.ts @@ -1,7 +1,7 @@ export * as ConfigError from "./error" import z from "zod" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" export const JsonError = NamedError.create( "ConfigJsonError", diff --git a/packages/opencode/src/config/markdown.ts b/packages/opencode/src/config/markdown.ts index 7cad69266..d782d655e 100644 --- a/packages/opencode/src/config/markdown.ts +++ b/packages/opencode/src/config/markdown.ts @@ -1,4 +1,4 @@ -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import matter from "gray-matter" import { z } from "zod" import { Filesystem } from "../util" diff --git a/packages/opencode/src/config/paths.ts b/packages/opencode/src/config/paths.ts index db4b914f7..572676fcc 100644 --- a/packages/opencode/src/config/paths.ts +++ b/packages/opencode/src/config/paths.ts @@ -7,7 +7,7 @@ import { Global } from "@/global" import { unique } from "remeda" import { JsonError } from "./error" import * as Effect from "effect/Effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" export const files = Effect.fn("ConfigPaths.projectFiles")(function* ( name: string, diff --git a/packages/opencode/src/config/plugin.ts b/packages/opencode/src/config/plugin.ts index 4277c1cd6..9667dbb59 100644 --- a/packages/opencode/src/config/plugin.ts +++ b/packages/opencode/src/config/plugin.ts @@ -1,4 +1,4 @@ -import { Glob } from "@opencode-ai/shared/util/glob" +import { Glob } from "@opencode-ai/core/util/glob" import { Schema } from "effect" import { pathToFileURL } from "url" import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared" diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index 107f2d990..e1ebb613e 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -12,7 +12,7 @@ import { Flag } from "@/flag/flag" import { Log } from "@/util" import { Filesystem } from "@/util" import { ProjectID } from "@/project/schema" -import { Slug } from "@opencode-ai/shared/util/slug" +import { Slug } from "@opencode-ai/core/util/slug" import { WorkspaceTable } from "./workspace.sql" import { getAdaptor } from "./adaptors" import { type WorkspaceInfo, WorkspaceInfo as WorkspaceInfoSchema } from "./types" diff --git a/packages/opencode/src/effect/app-runtime.ts b/packages/opencode/src/effect/app-runtime.ts index d68e00a32..6c9d949b8 100644 --- a/packages/opencode/src/effect/app-runtime.ts +++ b/packages/opencode/src/effect/app-runtime.ts @@ -2,7 +2,7 @@ import { Layer, ManagedRuntime } from "effect" import { attach } from "./run-service" import * as Observability from "./observability" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Bus } from "@/bus" import { Auth } from "@/auth" import { Account } from "@/account/account" diff --git a/packages/opencode/src/file/ignore.ts b/packages/opencode/src/file/ignore.ts index efce87280..68c359b9a 100644 --- a/packages/opencode/src/file/ignore.ts +++ b/packages/opencode/src/file/ignore.ts @@ -1,4 +1,4 @@ -import { Glob } from "@opencode-ai/shared/util/glob" +import { Glob } from "@opencode-ai/core/util/glob" const FOLDERS = new Set([ "node_modules", diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 05e2ce359..4710fd76d 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -1,7 +1,7 @@ import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Git } from "@/git" import { Effect, Layer, Context, Schema, Scope } from "effect" import * as Stream from "effect/Stream" diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 64a2e3d8e..e31f53733 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -1,5 +1,5 @@ import path from "path" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Cause, Context, Effect, Fiber, Layer, Queue, Schema, Stream } from "effect" import type { PlatformError } from "effect/PlatformError" import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http" diff --git a/packages/opencode/src/global/index.ts b/packages/opencode/src/global/index.ts index 27bac598f..7f48a0f88 100644 --- a/packages/opencode/src/global/index.ts +++ b/packages/opencode/src/global/index.ts @@ -3,7 +3,7 @@ import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir" import path from "path" import os from "os" import { Filesystem } from "../util" -import { Flock } from "@opencode-ai/shared/util/flock" +import { Flock } from "@opencode-ai/core/util/flock" const app = "opencode" diff --git a/packages/opencode/src/ide/index.ts b/packages/opencode/src/ide/index.ts index f9ce1ec63..4a2576f68 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -1,7 +1,7 @@ import { BusEvent } from "@/bus/bus-event" import z from "zod" import { Schema } from "effect" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { Log } from "../util" import { Process } from "@/util" diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 0a3a927b4..c27f6b740 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -12,7 +12,7 @@ import { ModelsCommand } from "./cli/cmd/models" import { UI } from "./cli/ui" import { Installation } from "./installation" import { InstallationVersion } from "./installation/version" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { FormatError } from "./cli/error" import { ServeCommand } from "./cli/cmd/serve" import { Filesystem } from "./util" diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index e8050babf..4eaa32f77 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -10,7 +10,7 @@ import { LANGUAGE_EXTENSIONS } from "./language" import z from "zod" import { Schema } from "effect" import type * as LSPServer from "./server" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { withTimeout } from "../util/timeout" import { Filesystem } from "../util" diff --git a/packages/opencode/src/lsp/lsp.ts b/packages/opencode/src/lsp/lsp.ts index 7741ff60e..5078cbadb 100644 --- a/packages/opencode/src/lsp/lsp.ts +++ b/packages/opencode/src/lsp/lsp.ts @@ -12,7 +12,7 @@ import { Process } from "../util" import { spawn as lspspawn } from "./launch" import { Effect, Layer, Context, Schema } from "effect" import { InstanceState } from "@/effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { withStatics } from "@/util/schema" import { zod, ZodOverride } from "@/util/effect-zod" diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 7faaeb42f..ef001888e 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -11,7 +11,7 @@ import { Flag } from "../flag/flag" import { Archive } from "../util" import { Process } from "../util" import { which } from "../util/which" -import { Module } from "@opencode-ai/shared/util/module" +import { Module } from "@opencode-ai/core/util/module" import { spawn } from "./launch" import { Npm } from "../npm" diff --git a/packages/opencode/src/mcp/auth.ts b/packages/opencode/src/mcp/auth.ts index efb046d7a..0a57fa141 100644 --- a/packages/opencode/src/mcp/auth.ts +++ b/packages/opencode/src/mcp/auth.ts @@ -2,7 +2,7 @@ import path from "path" import z from "zod" import { Global } from "../global" import { Effect, Layer, Context } from "effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" export const Tokens = z.object({ accessToken: z.string(), diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 3c6816c5b..8b2562dc4 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -12,12 +12,12 @@ import { import { Config } from "../config" import { ConfigMCP } from "../config/mcp" import { Log } from "../util" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import z from "zod/v4" import { Installation } from "../installation" import { InstallationVersion } from "../installation/version" import { withTimeout } from "@/util/timeout" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { McpOAuthProvider } from "./oauth-provider" import { McpOAuthCallback } from "./oauth-callback" import { McpAuth } from "./auth" diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 4b1f80707..d876b0e52 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -8,9 +8,9 @@ import Config from "@npmcli/config" import { definitions, flatten, nerfDarts, shorthands } from "@npmcli/config/lib/definitions/index.js" import { Effect, Schema, Context, Layer, Option, FileSystem, Stream } from "effect" import { NodeFileSystem } from "@effect/platform-node" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { Global } from "@opencode-ai/shared/global" -import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { Global } from "@opencode-ai/core/global" +import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import * as CrossSpawnSpawner from "../effect/cross-spawn-spawner" diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index dd2a78469..4587d8fb1 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -12,7 +12,7 @@ import { createOpencodeClient } from "@opencode-ai/sdk" import { Flag } from "../flag/flag" import { CodexAuthPlugin } from "./codex" import { Session } from "../session" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { CopilotAuthPlugin } from "./github-copilot/copilot" import { gitlabAuthPlugin as GitlabAuthPlugin } from "opencode-gitlab-auth" import { PoeAuthPlugin } from "opencode-poe-auth" diff --git a/packages/opencode/src/plugin/install.ts b/packages/opencode/src/plugin/install.ts index 0525a7ba0..87798f56d 100644 --- a/packages/opencode/src/plugin/install.ts +++ b/packages/opencode/src/plugin/install.ts @@ -10,7 +10,7 @@ import { import * as ConfigPaths from "@/config/paths" import { Global } from "@/global" import { Filesystem } from "@/util" -import { Flock } from "@opencode-ai/shared/util/flock" +import { Flock } from "@opencode-ai/core/util/flock" import { isRecord } from "@/util/record" import { parsePluginSpecifier, readPackageThemes, readPluginPackage, resolvePluginTarget } from "./shared" diff --git a/packages/opencode/src/plugin/meta.ts b/packages/opencode/src/plugin/meta.ts index 86ad8fbab..4c14a0dec 100644 --- a/packages/opencode/src/plugin/meta.ts +++ b/packages/opencode/src/plugin/meta.ts @@ -4,7 +4,7 @@ import { fileURLToPath } from "url" import { Flag } from "@/flag/flag" import { Global } from "@/global" import { Filesystem } from "@/util" -import { Flock } from "@opencode-ai/shared/util/flock" +import { Flock } from "@opencode-ai/core/util/flock" import { parsePluginSpecifier, pluginSource } from "./shared" diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts index 1c5109620..cd2013674 100644 --- a/packages/opencode/src/project/instance.ts +++ b/packages/opencode/src/project/instance.ts @@ -1,7 +1,7 @@ import { GlobalBus } from "@/bus/global" import { disposeInstance } from "@/effect/instance-registry" import { makeRuntime } from "@/effect/run-service" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { iife } from "@/util/iife" import { Log } from "@/util" import { LocalContext } from "../util" diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 70a959064..88d033921 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -11,7 +11,7 @@ import { ProjectID } from "./schema" import { Effect, Layer, Path, Scope, Context, Stream, Types, Schema } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" diff --git a/packages/opencode/src/project/vcs.ts b/packages/opencode/src/project/vcs.ts index 1c1da97bf..2fbab4f63 100644 --- a/packages/opencode/src/project/vcs.ts +++ b/packages/opencode/src/project/vcs.ts @@ -4,7 +4,7 @@ import path from "path" import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { FileWatcher } from "@/file/watcher" import { Git } from "@/git" import { Log } from "@/util" diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index 36c4d8c23..e52464d6d 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -6,8 +6,8 @@ import { Installation } from "../installation" import { Flag } from "../flag/flag" import { lazy } from "@/util/lazy" import { Filesystem } from "../util" -import { Flock } from "@opencode-ai/shared/util/flock" -import { Hash } from "@opencode-ai/shared/util/hash" +import { Flock } from "@opencode-ai/core/util/flock" +import { Hash } from "@opencode-ai/core/util/hash" // Try to import bundled snapshot (generated at build time) // Falls back to undefined in dev mode when snapshot doesn't exist diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 0fe53e6e4..d6ccbacfc 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -5,7 +5,7 @@ import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda" import { NoSuchModelError, type Provider as SDK } from "ai" import { Log } from "../util" import { Npm } from "../npm" -import { Hash } from "@opencode-ai/shared/util/hash" +import { Hash } from "@opencode-ai/core/util/hash" import { Plugin } from "../plugin" import { type LanguageModelV3 } from "@ai-sdk/provider" import * as ModelsDev from "./models" @@ -22,7 +22,7 @@ import { pathToFileURL } from "url" import { Effect, Layer, Context, Schema, Types } from "effect" import { EffectBridge } from "@/effect" import { InstanceState } from "@/effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { isRecord } from "@/util/record" import { withStatics } from "@/util/schema" diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 604fa77fb..918f4f86c 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -4,7 +4,7 @@ import { InstanceState } from "@/effect" import { Instance } from "@/project/instance" import type { Proc } from "#pty" import { Log } from "../util" -import { lazy } from "@opencode-ai/shared/util/lazy" +import { lazy } from "@opencode-ai/core/util/lazy" import { Shell } from "@/shell/shell" import { Plugin } from "@/plugin" import { PtyID } from "./schema" diff --git a/packages/opencode/src/server/middleware.ts b/packages/opencode/src/server/middleware.ts index b67d15f55..55d9dee79 100644 --- a/packages/opencode/src/server/middleware.ts +++ b/packages/opencode/src/server/middleware.ts @@ -1,5 +1,5 @@ import { Provider } from "../provider" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { NotFoundError } from "../storage" import { Session } from "../session" import type { ContentfulStatusCode } from "hono/utils/http-status" diff --git a/packages/opencode/src/server/routes/instance/middleware.ts b/packages/opencode/src/server/routes/instance/middleware.ts index b963268d6..19918b8b4 100644 --- a/packages/opencode/src/server/routes/instance/middleware.ts +++ b/packages/opencode/src/server/routes/instance/middleware.ts @@ -2,7 +2,7 @@ import type { MiddlewareHandler } from "hono" import { Instance } from "@/project/instance" import { InstanceBootstrap } from "@/project/bootstrap" import { AppRuntime } from "@/effect/app-runtime" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { WorkspaceContext } from "@/control-plane/workspace-context" import { WorkspaceID } from "@/control-plane/schema" diff --git a/packages/opencode/src/server/routes/instance/session.ts b/packages/opencode/src/server/routes/instance/session.ts index 4f4f8ed86..52a803467 100644 --- a/packages/opencode/src/server/routes/instance/session.ts +++ b/packages/opencode/src/server/routes/instance/session.ts @@ -25,7 +25,7 @@ import { errors } from "../../error" import { lazy } from "@/util/lazy" import { zodObject } from "@/util/effect-zod" import { Bus } from "@/bus" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { jsonRequest, runRequest } from "./trace" const log = Log.create({ service: "server" }) diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index 122644c1f..a18a55584 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -5,7 +5,7 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/ import { Config } from "@/config" import { InstanceState } from "@/effect" import { Flag } from "@/flag/flag" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { withTransientReadRetry } from "@/util/effect-http-client" import { Global } from "../global" import { Log } from "../util" diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index d04645b73..8a2d352a5 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -1,7 +1,7 @@ import { BusEvent } from "@/bus/bus-event" import { SessionID, MessageID, PartID } from "./schema" import z from "zod" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai" import { LSP } from "../lsp" import { Snapshot } from "@/snapshot" diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 3d07a96ec..8e227e602 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -33,14 +33,14 @@ import { Command } from "../command" import { pathToFileURL, fileURLToPath } from "url" import { ConfigMarkdown } from "../config" import { SessionSummary } from "./summary" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { SessionProcessor } from "./processor" import { Tool } from "@/tool" import { Permission } from "@/permission" import { SessionStatus } from "./status" import { LLM } from "./llm" import { Shell } from "@/shell/shell" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Truncate } from "@/tool" import { decodeDataUrl } from "@/util/data-url" import { Process } from "@/util" diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index 12fd4d345..e81e19737 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -1,4 +1,4 @@ -import type { NamedError } from "@opencode-ai/shared/util/error" +import type { NamedError } from "@opencode-ai/core/util/error" import { Cause, Clock, Duration, Effect, Schedule } from "effect" import { MessageV2 } from "./message-v2" import { iife } from "@/util/iife" diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index f4fe3bf8b..472339b05 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -1,4 +1,4 @@ -import { Slug } from "@opencode-ai/shared/util/slug" +import { Slug } from "@opencode-ai/core/util/slug" import path from "path" import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" diff --git a/packages/opencode/src/skill/discovery.ts b/packages/opencode/src/skill/discovery.ts index debd68dd3..e620de983 100644 --- a/packages/opencode/src/skill/discovery.ts +++ b/packages/opencode/src/skill/discovery.ts @@ -2,7 +2,7 @@ import { NodePath } from "@effect/platform-node" import { Effect, Layer, Path, Schema, Context } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { withTransientReadRetry } from "@/util/effect-http-client" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Global } from "../global" import { Log } from "../util" diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index dd5cc4e5d..e5282e250 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -3,17 +3,17 @@ import path from "path" import { pathToFileURL } from "url" import z from "zod" import { Effect, Layer, Context } from "effect" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import type { Agent } from "@/agent/agent" import { Bus } from "@/bus" import { InstanceState } from "@/effect" import { Flag } from "@/flag/flag" import { Global } from "@/global" import { Permission } from "@/permission" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Config } from "../config" import { ConfigMarkdown } from "../config" -import { Glob } from "@opencode-ai/shared/util/glob" +import { Glob } from "@opencode-ai/core/util/glob" import { Log } from "../util" import { Discovery } from "./discovery" diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index ddc4cb29e..50804ca2b 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -5,8 +5,8 @@ import path from "path" import z from "zod" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" import { InstanceState } from "@/effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { Hash } from "@opencode-ai/shared/util/hash" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { Hash } from "@opencode-ai/core/util/hash" import { Config } from "../config" import { Global } from "../global" import { Log } from "../util" diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts index 2c0076452..67f5f1289 100644 --- a/packages/opencode/src/storage/db.ts +++ b/packages/opencode/src/storage/db.ts @@ -6,7 +6,7 @@ import { LocalContext } from "../util" import { lazy } from "../util/lazy" import { Global } from "../global" import { Log } from "../util" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import z from "zod" import path from "path" import { readFileSync, readdirSync, existsSync } from "fs" diff --git a/packages/opencode/src/storage/json-migration.ts b/packages/opencode/src/storage/json-migration.ts index 05588db0f..20ca3ff53 100644 --- a/packages/opencode/src/storage/json-migration.ts +++ b/packages/opencode/src/storage/json-migration.ts @@ -8,7 +8,7 @@ import { SessionShareTable } from "../share/share.sql" import path from "path" import { existsSync } from "fs" import { Filesystem } from "../util" -import { Glob } from "@opencode-ai/shared/util/glob" +import { Glob } from "@opencode-ai/core/util/glob" const log = Log.create({ service: "json-migration" }) diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts index b1685e689..8f6332677 100644 --- a/packages/opencode/src/storage/storage.ts +++ b/packages/opencode/src/storage/storage.ts @@ -1,9 +1,9 @@ import { Log } from "../util" import path from "path" import { Global } from "../global" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import z from "zod" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Effect, Exit, Layer, Option, RcMap, Schema, Context, TxReentrantLock } from "effect" import { Git } from "@/git" diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts index 72f24a3f6..9a009189d 100644 --- a/packages/opencode/src/tool/apply_patch.ts +++ b/packages/opencode/src/tool/apply_patch.ts @@ -9,7 +9,7 @@ import { createTwoFilesPatch, diffLines } from "diff" import { assertExternalDirectoryEffect } from "./external-directory" import { trimDiff } from "./edit" import { LSP } from "../lsp" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import DESCRIPTION from "./apply_patch.txt" import { File } from "../file" import { Format } from "../format" diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 0a7e1a6dc..1b8875326 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -9,7 +9,7 @@ import { Instance } from "../project/instance" import { lazy } from "@/util/lazy" import { Language, type Node } from "web-tree-sitter" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { fileURLToPath } from "url" import { Flag } from "@/flag/flag" import { Shell } from "@/shell/shell" diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index cfff5a0a3..04a84a388 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -16,7 +16,7 @@ import { Format } from "../format" import { Instance } from "../project/instance" import { Snapshot } from "@/snapshot" import { assertExternalDirectoryEffect } from "./external-directory" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import * as Bom from "@/util/bom" function normalizeLineEndings(text: string): string { diff --git a/packages/opencode/src/tool/external-directory.ts b/packages/opencode/src/tool/external-directory.ts index 88b73da50..b8def1d75 100644 --- a/packages/opencode/src/tool/external-directory.ts +++ b/packages/opencode/src/tool/external-directory.ts @@ -4,7 +4,7 @@ import { EffectLogger } from "@/effect" import { InstanceState } from "@/effect" import type * as Tool from "./tool" import { Instance } from "../project/instance" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" type Kind = "file" | "directory" diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts index aeecfecb7..984c13d41 100644 --- a/packages/opencode/src/tool/glob.ts +++ b/packages/opencode/src/tool/glob.ts @@ -2,7 +2,7 @@ import path from "path" import { Effect, Option, Schema } from "effect" import * as Stream from "effect/Stream" import { InstanceState } from "@/effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Ripgrep } from "../file/ripgrep" import { assertExternalDirectoryEffect } from "./external-directory" import DESCRIPTION from "./glob.txt" diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts index 416005431..844de6753 100644 --- a/packages/opencode/src/tool/grep.ts +++ b/packages/opencode/src/tool/grep.ts @@ -2,7 +2,7 @@ import path from "path" import { Schema } from "effect" import { Effect, Option } from "effect" import { InstanceState } from "@/effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Ripgrep } from "../file/ripgrep" import { assertExternalDirectoryEffect } from "./external-directory" import DESCRIPTION from "./grep.txt" diff --git a/packages/opencode/src/tool/lsp.ts b/packages/opencode/src/tool/lsp.ts index 3bcae426a..bb3b50344 100644 --- a/packages/opencode/src/tool/lsp.ts +++ b/packages/opencode/src/tool/lsp.ts @@ -6,7 +6,7 @@ import DESCRIPTION from "./lsp.txt" import { Instance } from "../project/instance" import { pathToFileURL } from "url" import { assertExternalDirectoryEffect } from "./external-directory" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" const operations = [ "goToDefinition", diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index d0995626c..e89f03109 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -3,7 +3,7 @@ import { createReadStream } from "fs" import * as path from "path" import { createInterface } from "readline" import * as Tool from "./tool" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { LSP } from "../lsp" import DESCRIPTION from "./read.txt" import { Instance } from "../project/instance" diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 539ad6320..629c57965 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -28,7 +28,7 @@ import { Log } from "@/util" import { LspTool } from "./lsp" import * as Truncate from "./truncate" import { ApplyPatchTool } from "./apply_patch" -import { Glob } from "@opencode-ai/shared/util/glob" +import { Glob } from "@opencode-ai/core/util/glob" import path from "path" import { pathToFileURL } from "url" import { Effect, Layer, Context } from "effect" @@ -42,7 +42,7 @@ import { Question } from "../question" import { Todo } from "../session/todo" import { LSP } from "../lsp" import { Instruction } from "../session/instruction" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Bus } from "../bus" import { Agent } from "../agent/agent" import { Skill } from "../skill" diff --git a/packages/opencode/src/tool/truncate.ts b/packages/opencode/src/tool/truncate.ts index e0d846858..191d96795 100644 --- a/packages/opencode/src/tool/truncate.ts +++ b/packages/opencode/src/tool/truncate.ts @@ -2,7 +2,7 @@ import { NodePath } from "@effect/platform-node" import { Cause, Duration, Effect, Layer, Option, Schedule, Context } from "effect" import path from "path" import type { Agent } from "../agent/agent" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { evaluate } from "@/permission/evaluate" import { Config } from "../config" import { Identifier } from "../id/id" diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index b52f4a164..d977325f1 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -9,7 +9,7 @@ import { Bus } from "../bus" import { File } from "../file" import { FileWatcher } from "../file/watcher" import { Format } from "../format" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Instance } from "../project/instance" import { trimDiff } from "./edit" import { assertExternalDirectoryEffect } from "./external-directory" diff --git a/packages/opencode/src/util/bom.ts b/packages/opencode/src/util/bom.ts index 484228f3d..79de91578 100644 --- a/packages/opencode/src/util/bom.ts +++ b/packages/opencode/src/util/bom.ts @@ -1,5 +1,5 @@ import { Effect } from "effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" const BOM_CODE = 0xfeff const BOM = String.fromCharCode(BOM_CODE) diff --git a/packages/opencode/src/util/filesystem.ts b/packages/opencode/src/util/filesystem.ts index 6c4d45522..6225c80d2 100644 --- a/packages/opencode/src/util/filesystem.ts +++ b/packages/opencode/src/util/filesystem.ts @@ -4,7 +4,7 @@ import { realpathSync } from "fs" import { dirname, join, relative, resolve as pathResolve, win32 } from "path" import { Readable } from "stream" import { pipeline } from "stream/promises" -import { Glob } from "@opencode-ai/shared/util/glob" +import { Glob } from "@opencode-ai/core/util/glob" // Fast sync version for metadata checks export async function exists(p: string): Promise { diff --git a/packages/opencode/src/util/log.ts b/packages/opencode/src/util/log.ts index 7c1581bfc..e335a8b43 100644 --- a/packages/opencode/src/util/log.ts +++ b/packages/opencode/src/util/log.ts @@ -3,7 +3,7 @@ import fs from "fs/promises" import { createWriteStream } from "fs" import { Global } from "../global" import z from "zod" -import { Glob } from "@opencode-ai/shared/util/glob" +import { Glob } from "@opencode-ai/core/util/glob" export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" }) export type Level = z.infer diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index e122fe453..7539e8d58 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -1,5 +1,5 @@ import z from "zod" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { Global } from "../global" import { Instance } from "../project/instance" import { InstanceBootstrap } from "../project/bootstrap" @@ -8,7 +8,7 @@ import { Database, eq } from "../storage" import { ProjectTable } from "../project/project.sql" import type { ProjectID } from "../project/schema" import { Log } from "../util" -import { Slug } from "@opencode-ai/shared/util/slug" +import { Slug } from "@opencode-ai/core/util/slug" import { errorMessage } from "../util/error" import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" @@ -16,7 +16,7 @@ import { Git } from "@/git" import { Effect, Layer, Path, Schema, Scope, Context, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { BootstrapRuntime } from "@/effect/bootstrap-runtime" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" import { InstanceState } from "@/effect" diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 361ac0b5d..56b8e7acd 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -3,13 +3,13 @@ import { Effect, Layer, Option } from "effect" import { NodeFileSystem, NodePath } from "@effect/platform-node" import { Config, ConfigManaged } from "../../src/config" import { ConfigParse } from "../../src/config/parse" -import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" +import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { Instance } from "../../src/project/instance" import { Auth } from "../../src/auth" import { Account } from "../../src/account/account" import { AccessToken, AccountID, OrgID } from "../../src/account/schema" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Env } from "../../src/env" import { provideTmpdirInstance } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture" @@ -895,7 +895,7 @@ test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => { }) // Note: deduplication and serialization of npm installs is now handled by the -// shared Npm.Service (via EffectFlock). Those behaviors are tested in the shared +// core Npm.Service (via EffectFlock). Those behaviors are tested in the core // package's npm tests, not here. test("resolves scoped npm plugins in config", async () => { diff --git a/packages/opencode/test/filesystem/filesystem.test.ts b/packages/opencode/test/filesystem/filesystem.test.ts index 0bb4ba583..2d9271e87 100644 --- a/packages/opencode/test/filesystem/filesystem.test.ts +++ b/packages/opencode/test/filesystem/filesystem.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect } from "bun:test" import { Effect, Layer } from "effect" import { NodeFileSystem } from "@effect/platform-node" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { testEffect } from "../lib/effect" import path from "path" diff --git a/packages/opencode/test/fixture/flock-worker.ts b/packages/opencode/test/fixture/flock-worker.ts index 9954d290c..0b9c314c0 100644 --- a/packages/opencode/test/fixture/flock-worker.ts +++ b/packages/opencode/test/fixture/flock-worker.ts @@ -1,5 +1,5 @@ import fs from "fs/promises" -import { Flock } from "@opencode-ai/shared/util/flock" +import { Flock } from "@opencode-ai/core/util/flock" type Msg = { key: string diff --git a/packages/opencode/test/npm.test.ts b/packages/opencode/test/npm.test.ts index b27d668c8..09fa6b351 100644 --- a/packages/opencode/test/npm.test.ts +++ b/packages/opencode/test/npm.test.ts @@ -3,9 +3,9 @@ import path from "path" import { describe, expect, test } from "bun:test" import { Effect, Layer, Stream } from "effect" import { NodeFileSystem } from "@effect/platform-node" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { Global } from "@opencode-ai/shared/global" -import { EffectFlock } from "@opencode-ai/shared/util/effect-flock" +import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { Global } from "@opencode-ai/core/global" +import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { Npm } from "../src/npm" import { tmpdir } from "./fixture/fixture" diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index 080519a73..c61df3548 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -9,7 +9,7 @@ import { ProjectID } from "../../src/project/schema" import { Effect, Layer, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" void Log.init({ print: false }) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 911cb4415..451f1d004 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -4,7 +4,7 @@ import { expect } from "bun:test" import { Cause, Effect, Exit, Fiber, Layer } from "effect" import path from "path" import { fileURLToPath } from "url" -import { NamedError } from "@opencode-ai/shared/util/error" +import { NamedError } from "@opencode-ai/core/util/error" import { Agent as AgentSvc } from "../../src/agent/agent" import { Bus } from "../../src/bus" import { Command } from "../../src/command" @@ -21,7 +21,7 @@ import { Todo } from "../../src/session/todo" import { Session } from "../../src/session" import { LLM } from "../../src/session/llm" import { MessageV2 } from "../../src/session/message-v2" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { SessionCompaction } from "../../src/session/compaction" import { SessionSummary } from "../../src/session/summary" import { Instruction } from "../../src/session/instruction" diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 6ca8775f3..aa1a29ec1 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { NamedError } from "@opencode-ai/shared/util/error" +import type { NamedError } from "@opencode-ai/core/util/error" import { APICallError } from "ai" import { setTimeout as sleep } from "node:timers/promises" import { Effect, Schedule } from "effect" diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 651754733..c7e352262 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -51,7 +51,7 @@ import { SessionStatus } from "../../src/session/status" import { Snapshot } from "../../src/snapshot" import { ToolRegistry } from "../../src/tool" import { Truncate } from "../../src/tool" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { Format } from "../../src/format" diff --git a/packages/opencode/test/storage/storage.test.ts b/packages/opencode/test/storage/storage.test.ts index c35244bb7..0587b9dd6 100644 --- a/packages/opencode/test/storage/storage.test.ts +++ b/packages/opencode/test/storage/storage.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import path from "path" import { Effect, Exit, Layer } from "effect" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import { Git } from "../../src/git" import { Global } from "../../src/global" diff --git a/packages/opencode/test/tool/apply_patch.test.ts b/packages/opencode/test/tool/apply_patch.test.ts index fa8843213..f311b3d9b 100644 --- a/packages/opencode/test/tool/apply_patch.test.ts +++ b/packages/opencode/test/tool/apply_patch.test.ts @@ -5,7 +5,7 @@ import { Effect, ManagedRuntime, Layer } from "effect" import { ApplyPatchTool } from "../../src/tool/apply_patch" import { Instance } from "../../src/project/instance" import { LSP } from "../../src/lsp" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Format } from "../../src/format" import { Agent } from "../../src/agent/agent" import { Bus } from "../../src/bus" diff --git a/packages/opencode/test/tool/bash.test.ts b/packages/opencode/test/tool/bash.test.ts index d66cfc3e3..fd35c9aeb 100644 --- a/packages/opencode/test/tool/bash.test.ts +++ b/packages/opencode/test/tool/bash.test.ts @@ -12,7 +12,7 @@ import { Agent } from "../../src/agent/agent" import { Truncate } from "../../src/tool" import { SessionID, MessageID } from "../../src/session/schema" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Plugin } from "../../src/plugin" const runtime = ManagedRuntime.make( diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts index 82e1b4a7f..fb2080591 100644 --- a/packages/opencode/test/tool/edit.test.ts +++ b/packages/opencode/test/tool/edit.test.ts @@ -6,7 +6,7 @@ import { EditTool } from "../../src/tool/edit" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" import { LSP } from "../../src/lsp" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Format } from "../../src/format" import { Agent } from "../../src/agent/agent" import { Bus } from "../../src/bus" diff --git a/packages/opencode/test/tool/glob.test.ts b/packages/opencode/test/tool/glob.test.ts index 87d35715d..c37e7b35f 100644 --- a/packages/opencode/test/tool/glob.test.ts +++ b/packages/opencode/test/tool/glob.test.ts @@ -5,7 +5,7 @@ import { GlobTool } from "../../src/tool/glob" import { SessionID, MessageID } from "../../src/session/schema" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Truncate } from "../../src/tool" import { Agent } from "../../src/agent/agent" import { provideTmpdirInstance } from "../fixture/fixture" diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index 388828f6e..a279574e1 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -8,7 +8,7 @@ import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import { Truncate } from "../../src/tool" import { Agent } from "../../src/agent/agent" import { Ripgrep } from "../../src/file/ripgrep" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { testEffect } from "../lib/effect" const it = testEffect( diff --git a/packages/opencode/test/tool/lsp.test.ts b/packages/opencode/test/tool/lsp.test.ts index 57b8fc6e8..b9d48e69a 100644 --- a/packages/opencode/test/tool/lsp.test.ts +++ b/packages/opencode/test/tool/lsp.test.ts @@ -3,7 +3,7 @@ import { Effect, Layer } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { LSP } from "../../src/lsp" import { Permission } from "../../src/permission" import { Instance } from "../../src/project/instance" diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index 42817d15d..7c3bf51fe 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -3,7 +3,7 @@ import { Cause, Effect, Exit, Layer } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { LSP } from "../../src/lsp" import { Permission } from "../../src/permission" import { Instance } from "../../src/project/instance" diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts index 36131f959..0714d2d02 100644 --- a/packages/opencode/test/tool/write.test.ts +++ b/packages/opencode/test/tool/write.test.ts @@ -5,7 +5,7 @@ import fs from "fs/promises" import { WriteTool } from "../../src/tool/write" import { Instance } from "../../src/project/instance" import { LSP } from "../../src/lsp" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Bus } from "../../src/bus" import { Format } from "../../src/format" import { Truncate } from "../../src/tool" diff --git a/packages/opencode/test/util/glob.test.ts b/packages/opencode/test/util/glob.test.ts index e982d5194..4ed2f71f3 100644 --- a/packages/opencode/test/util/glob.test.ts +++ b/packages/opencode/test/util/glob.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect } from "bun:test" import path from "path" import fs from "fs/promises" -import { Glob } from "@opencode-ai/shared/util/glob" +import { Glob } from "@opencode-ai/core/util/glob" import { tmpdir } from "../fixture/fixture" describe("Glob", () => { diff --git a/packages/opencode/test/util/module.test.ts b/packages/opencode/test/util/module.test.ts index 6725149c7..19c7958fc 100644 --- a/packages/opencode/test/util/module.test.ts +++ b/packages/opencode/test/util/module.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" import path from "path" -import { Module } from "@opencode-ai/shared/util/module" +import { Module } from "@opencode-ai/core/util/module" import { Filesystem } from "../../src/util" import { tmpdir } from "../fixture/fixture" diff --git a/packages/ui/package.json b/packages/ui/package.json index 9feb8c035..da7a0f673 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -44,7 +44,7 @@ "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", - "@opencode-ai/shared": "workspace:*", + "@opencode-ai/core": "workspace:*", "@pierre/diffs": "catalog:", "@shikijs/transformers": "3.9.2", "@solid-primitives/bounds": "0.1.3", diff --git a/packages/ui/src/components/file.tsx b/packages/ui/src/components/file.tsx index 633b23b70..97d4d69f7 100644 --- a/packages/ui/src/components/file.tsx +++ b/packages/ui/src/components/file.tsx @@ -1,4 +1,4 @@ -import { sampledChecksum } from "@opencode-ai/shared/util/encode" +import { sampledChecksum } from "@opencode-ai/core/util/encode" import { DEFAULT_VIRTUAL_FILE_METRICS, type DiffLineAnnotation, diff --git a/packages/ui/src/components/line-comment.tsx b/packages/ui/src/components/line-comment.tsx index e20da5a8d..e5a7af9cb 100644 --- a/packages/ui/src/components/line-comment.tsx +++ b/packages/ui/src/components/line-comment.tsx @@ -1,5 +1,5 @@ import { useFilteredList } from "@opencode-ai/ui/hooks" -import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" +import { getDirectory, getFilename } from "@opencode-ai/core/util/path" import { createSignal, For, onMount, Show, splitProps, type JSX } from "solid-js" import { Button } from "./button" import { FileIcon } from "./file-icon" diff --git a/packages/ui/src/components/markdown.tsx b/packages/ui/src/components/markdown.tsx index 28653512e..56e2d9d70 100644 --- a/packages/ui/src/components/markdown.tsx +++ b/packages/ui/src/components/markdown.tsx @@ -2,7 +2,7 @@ import { useMarked } from "../context/marked" import { useI18n } from "../context/i18n" import DOMPurify from "dompurify" import morphdom from "morphdom" -import { checksum } from "@opencode-ai/shared/util/encode" +import { checksum } from "@opencode-ai/core/util/encode" import { ComponentProps, createEffect, createResource, createSignal, onCleanup, splitProps } from "solid-js" import { isServer } from "solid-js/web" import { stream } from "./markdown-stream" diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 9c0c90c00..013272205 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -45,8 +45,8 @@ import { Checkbox } from "./checkbox" import { DiffChanges } from "./diff-changes" import { Markdown } from "./markdown" import { ImagePreview } from "./image-preview" -import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/shared/util/path" -import { checksum } from "@opencode-ai/shared/util/encode" +import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { checksum } from "@opencode-ai/core/util/encode" import { Tooltip } from "./tooltip" import { IconButton } from "./icon-button" import { Spinner } from "./spinner" diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 94bca6727..949402f43 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -11,8 +11,8 @@ import { Tooltip } from "./tooltip" import { ScrollView } from "./scroll-view" import { useFileComponent } from "../context/file" import { useI18n } from "../context/i18n" -import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" -import { checksum } from "@opencode-ai/shared/util/encode" +import { getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { checksum } from "@opencode-ai/core/util/encode" import { createEffect, createMemo, For, Match, onCleanup, Show, Switch, untrack, type JSX } from "solid-js" import { createStore } from "solid-js/store" import { type FileContent, type SnapshotFileDiff, type VcsFileDiff } from "@opencode-ai/sdk/v2" diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 61123b180..b35f718ef 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -8,8 +8,8 @@ import type { SessionStatus } from "@opencode-ai/sdk/v2" import { useData } from "../context" import { useFileComponent } from "../context/file" -import { Binary } from "@opencode-ai/shared/util/binary" -import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" +import { Binary } from "@opencode-ai/core/util/binary" +import { getDirectory, getFilename } from "@opencode-ai/core/util/path" import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js" import { createStore } from "solid-js/store" import { Dynamic } from "solid-js/web" From 1d728fc627b44b34179d4e53963b643fac867bf1 Mon Sep 17 00:00:00 2001 From: Dax Date: Sat, 25 Apr 2026 11:08:19 -0400 Subject: [PATCH 159/426] feat: add startup debug command (#24310) --- packages/opencode/src/cli/cmd/debug/index.ts | 2 ++ packages/opencode/src/cli/cmd/debug/startup.ts | 11 +++++++++++ 2 files changed, 13 insertions(+) create mode 100644 packages/opencode/src/cli/cmd/debug/startup.ts diff --git a/packages/opencode/src/cli/cmd/debug/index.ts b/packages/opencode/src/cli/cmd/debug/index.ts index 8da6ff559..e780c4ccb 100644 --- a/packages/opencode/src/cli/cmd/debug/index.ts +++ b/packages/opencode/src/cli/cmd/debug/index.ts @@ -9,6 +9,7 @@ import { ScrapCommand } from "./scrap" import { SkillCommand } from "./skill" import { SnapshotCommand } from "./snapshot" import { AgentCommand } from "./agent" +import { StartupCommand } from "./startup" export const DebugCommand = cmd({ command: "debug", @@ -22,6 +23,7 @@ export const DebugCommand = cmd({ .command(ScrapCommand) .command(SkillCommand) .command(SnapshotCommand) + .command(StartupCommand) .command(AgentCommand) .command(PathsCommand) .command({ diff --git a/packages/opencode/src/cli/cmd/debug/startup.ts b/packages/opencode/src/cli/cmd/debug/startup.ts new file mode 100644 index 000000000..27fd52469 --- /dev/null +++ b/packages/opencode/src/cli/cmd/debug/startup.ts @@ -0,0 +1,11 @@ +import { EOL } from "os" +import { cmd } from "../cmd" + +export const StartupCommand = cmd({ + command: "startup", + describe: "print startup timing", + builder: (yargs) => yargs, + handler() { + process.stdout.write(performance.now().toString() + EOL) + }, +}) From 62651c7114c8fe1b3ec9a2868f32abfc6278993f Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 15:16:42 +0000 Subject: [PATCH 160/426] 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 30a2bbfbd..6be836b12 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-V1Rt2k7ujkqGw4pDkn++WALTy1fAugvoKLhKvwFKkss=", - "aarch64-linux": "sha256-ho0AuGbJ1qw9Hvb3EbGC8f0lWqqgUslvda/wTe32MFo=", - "aarch64-darwin": "sha256-hdUyNmp+snwtnBckHXsPMgNFUYS1sYDdngkk+AXVqzc=", - "x86_64-darwin": "sha256-P57LpQNF8fplFKQBBIukhOKbIugbViyBUIUjClXohuk=" + "x86_64-linux": "sha256-Dql+RLvSi8gtq7exwetJ4nSjKjJuyKuAf/X8X4L9E9E=", + "aarch64-linux": "sha256-qykwmIBF9tjZBjFo84e8tFix+i49TFM7Pf5jP4lLgEw=", + "aarch64-darwin": "sha256-TteIy5TuuhYsBIQ4q/weUkRnao0Y3iiKvf+noOYPuvg=", + "x86_64-darwin": "sha256-RbAqhuENpO5pT2oVUtVQ8JtUta/5hSnoqTzc3zPRCJc=" } } From a9740b9133a8056f5992b17f1b3fde15cc039f8d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 13:30:12 -0400 Subject: [PATCH 161/426] fix(config): preserve permission order with Effect decode (#24308) --- packages/opencode/src/config/agent.ts | 39 +++++------ packages/opencode/src/config/config.ts | 42 ++++++------ packages/opencode/src/config/parse.ts | 48 +++++++++++++- packages/opencode/src/config/permission.ts | 37 +---------- packages/opencode/test/config/config.test.ts | 70 +++++++++++++++++--- 5 files changed, 146 insertions(+), 90 deletions(-) diff --git a/packages/opencode/src/config/agent.ts b/packages/opencode/src/config/agent.ts index a8693c8aa..1d1c66a13 100644 --- a/packages/opencode/src/config/agent.ts +++ b/packages/opencode/src/config/agent.ts @@ -1,17 +1,16 @@ export * as ConfigAgent from "./agent" -import { Schema } from "effect" -import z from "zod" +import { Exit, Schema, SchemaGetter } from "effect" import { Bus } from "@/bus" import { zod } from "@/util/effect-zod" -import { PositiveInt } from "@/util/schema" +import { PositiveInt, withStatics } from "@/util/schema" import { Log } from "../util" import { NamedError } from "@opencode-ai/core/util/error" import { Glob } from "@opencode-ai/core/util/glob" import { configEntryNameFromPath } from "./entry-name" -import { InvalidError } from "./error" import * as ConfigMarkdown from "./markdown" import { ConfigModelID } from "./model-id" +import { ConfigParse } from "./parse" import { ConfigPermission } from "./permission" const log = Log.create({ service: "config" }) @@ -77,7 +76,7 @@ const KNOWN_KEYS = new Set([ // - Translate the deprecated `tools: { name: boolean }` map into the new // `permission` shape (write-adjacent tools collapse into `permission.edit`). // - Coalesce `steps ?? maxSteps` so downstream can ignore the deprecated alias. -const normalize = (agent: z.infer) => { +const normalize = (agent: Schema.Schema.Type): Schema.Schema.Type => { const options: Record = { ...agent.options } for (const [key, value] of Object.entries(agent)) { if (!KNOWN_KEYS.has(key)) options[key] = value @@ -98,14 +97,15 @@ const normalize = (agent: z.infer) => { return { ...agent, options, permission, ...(steps !== undefined ? { steps } : {}) } } -export const Info = zod(AgentSchema).transform(normalize).meta({ ref: "AgentConfig" }) as unknown as z.ZodType< - Omit>>, "options" | "permission" | "steps"> & { - options?: Record - permission?: ConfigPermission.Info - steps?: number - } -> -export type Info = z.infer +export const Info = AgentSchema.pipe( + Schema.decodeTo(AgentSchema, { + decode: SchemaGetter.transform(normalize), + encode: SchemaGetter.passthrough({ strict: false }), + }), +) + .annotate({ identifier: "AgentConfig" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Schema.Schema.Type export async function load(dir: string) { const result: Record = {} @@ -134,12 +134,7 @@ export async function load(dir: string) { ...md.data, prompt: md.content.trim(), } - const parsed = Info.safeParse(config) - if (parsed.success) { - result[config.name] = parsed.data - continue - } - throw new InvalidError({ path: item, issues: parsed.error.issues }, { cause: parsed.error }) + result[config.name] = ConfigParse.effectSchema(Info, config, item) } return result } @@ -168,10 +163,10 @@ export async function loadMode(dir: string) { ...md.data, prompt: md.content.trim(), } - const parsed = Info.safeParse(config) - if (parsed.success) { + const parsed = Schema.decodeUnknownExit(Info)(config, { errors: "all", propertyOrder: "original" }) + if (Exit.isSuccess(parsed)) { result[config.name] = { - ...parsed.data, + ...parsed.value, mode: "primary" as const, } } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 3238287be..70ba14464 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -24,7 +24,7 @@ import { InstanceState } from "@/effect" import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "effect" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { InstanceRef } from "@/effect/instance-ref" -import { zod, ZodOverride } from "@/util/effect-zod" +import { zod } from "@/util/effect-zod" import { NonNegativeInt, PositiveInt, withStatics, type DeepMutable } from "@/util/schema" import { ConfigAgent } from "./agent" import { ConfigCommand } from "./command" @@ -81,12 +81,10 @@ export const Server = ConfigServer.Server.zod export const Layout = ConfigLayout.Layout.zod export type Layout = ConfigLayout.Layout -// Schemas that still live at the zod layer (have .transform / .preprocess / -// .meta not expressible in current Effect Schema) get referenced via a -// ZodOverride-annotated Schema.Any. Walker sees the annotation and emits the -// exact zod directly, preserving component $refs. -const AgentRef = Schema.Any.annotate({ [ZodOverride]: ConfigAgent.Info }) -const LogLevelRef = Schema.Any.annotate({ [ZodOverride]: Log.Level }) +const LogLevelRef = Schema.Literals(["DEBUG", "INFO", "WARN", "ERROR"]).annotate({ + identifier: "LogLevel", + description: "Log level", +}) // The Effect Schema is the canonical source of truth. The `.zod` compatibility // surface is derived so existing Hono validators keep working without a parallel @@ -152,27 +150,27 @@ export const Info = Schema.Struct({ mode: Schema.optional( Schema.StructWithRest( Schema.Struct({ - build: Schema.optional(AgentRef), - plan: Schema.optional(AgentRef), + build: Schema.optional(ConfigAgent.Info), + plan: Schema.optional(ConfigAgent.Info), }), - [Schema.Record(Schema.String, AgentRef)], + [Schema.Record(Schema.String, ConfigAgent.Info)], ), ).annotate({ description: "@deprecated Use `agent` field instead." }), agent: Schema.optional( Schema.StructWithRest( Schema.Struct({ // primary - plan: Schema.optional(AgentRef), - build: Schema.optional(AgentRef), + plan: Schema.optional(ConfigAgent.Info), + build: Schema.optional(ConfigAgent.Info), // subagent - general: Schema.optional(AgentRef), - explore: Schema.optional(AgentRef), + general: Schema.optional(ConfigAgent.Info), + explore: Schema.optional(ConfigAgent.Info), // specialized - title: Schema.optional(AgentRef), - summary: Schema.optional(AgentRef), - compaction: Schema.optional(AgentRef), + title: Schema.optional(ConfigAgent.Info), + summary: Schema.optional(ConfigAgent.Info), + compaction: Schema.optional(ConfigAgent.Info), }), - [Schema.Record(Schema.String, AgentRef)], + [Schema.Record(Schema.String, ConfigAgent.Info)], ), ).annotate({ description: "Agent configuration, see https://opencode.ai/docs/agents" }), provider: Schema.optional(Schema.Record(Schema.String, ConfigProvider.Info)).annotate({ @@ -184,7 +182,7 @@ export const Info = Schema.Struct({ Schema.Union([ ConfigMCP.Info, // Matches the legacy `{ enabled: false }` form used to disable a server. - Schema.Any.annotate({ [ZodOverride]: z.object({ enabled: z.boolean() }).strict() }), + Schema.Struct({ enabled: Schema.Boolean }), ]), ), ).annotate({ description: "MCP (Model Context Protocol) server configurations" }), @@ -362,7 +360,7 @@ export const layer = Layer.effect( ), ) const parsed = ConfigParse.jsonc(expanded, source) - const data = ConfigParse.schema(Info.zod, normalizeLoadedConfig(parsed, source), source) + const data = ConfigParse.effectSchema(Info, normalizeLoadedConfig(parsed, source), source) if (!("path" in options)) return data yield* Effect.promise(() => resolveLoadedPlugins(data, options.path)) @@ -754,13 +752,13 @@ export const layer = Layer.effect( let next: Info if (!file.endsWith(".jsonc")) { - const existing = ConfigParse.schema(Info.zod, ConfigParse.jsonc(before, file), file) + const existing = ConfigParse.effectSchema(Info, ConfigParse.jsonc(before, file), file) const merged = mergeDeep(writable(existing), writable(config)) yield* fs.writeFileString(file, JSON.stringify(merged, null, 2)).pipe(Effect.orDie) next = merged } else { const updated = patchJsonc(before, writable(config)) - next = ConfigParse.schema(Info.zod, ConfigParse.jsonc(updated, file), file) + next = ConfigParse.effectSchema(Info, ConfigParse.jsonc(updated, file), file) yield* fs.writeFileString(file, updated).pipe(Effect.orDie) } diff --git a/packages/opencode/src/config/parse.ts b/packages/opencode/src/config/parse.ts index 7472029ea..935104789 100644 --- a/packages/opencode/src/config/parse.ts +++ b/packages/opencode/src/config/parse.ts @@ -1,10 +1,12 @@ export * as ConfigParse from "./parse" import { type ParseError as JsoncParseError, parse as parseJsoncImpl, printParseErrorCode } from "jsonc-parser" +import { Cause, Exit, Schema as EffectSchema, SchemaIssue } from "effect" import z from "zod" +import type { DeepMutable } from "@/util/schema" import { InvalidError, JsonError } from "./error" -type Schema = z.ZodType +type ZodSchema = z.ZodType export function jsonc(text: string, filepath: string): unknown { const errors: JsoncParseError[] = [] @@ -33,7 +35,7 @@ export function jsonc(text: string, filepath: string): unknown { return data } -export function schema(schema: Schema, data: unknown, source: string): T { +export function schema(schema: ZodSchema, data: unknown, source: string): T { const parsed = schema.safeParse(data) if (parsed.success) return parsed.data @@ -42,3 +44,45 @@ export function schema(schema: Schema, data: unknown, source: string): T { issues: parsed.error.issues, }) } + +export function effectSchema>( + schema: S, + data: unknown, + source: string, +): DeepMutable { + const extra = topLevelExtraKeys(schema, data) + if (extra.length) { + throw new InvalidError({ + path: source, + issues: [ + { + code: "unrecognized_keys", + keys: extra, + path: [], + message: `Unrecognized key${extra.length === 1 ? "" : "s"}: ${extra.join(", ")}`, + } as z.core.$ZodIssue, + ], + }) + } + + const decoded = EffectSchema.decodeUnknownExit(schema)(data, { errors: "all", propertyOrder: "original" }) + if (Exit.isSuccess(decoded)) return decoded.value as DeepMutable + const error = Cause.squash(decoded.cause) + + throw new InvalidError( + { + path: source, + issues: EffectSchema.isSchemaError(error) + ? (SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues as z.core.$ZodIssue[]) + : ([{ code: "custom", message: String(error), path: [] }] as z.core.$ZodIssue[]), + }, + { cause: error }, + ) +} + +function topLevelExtraKeys(schema: EffectSchema.Top, data: unknown) { + if (typeof data !== "object" || data === null || Array.isArray(data)) return [] + if (schema.ast._tag !== "Objects" || schema.ast.indexSignatures.length > 0) return [] + const known = new Set(schema.ast.propertySignatures.map((item) => String(item.name))) + return Object.keys(data).filter((key) => !known.has(key)) +} diff --git a/packages/opencode/src/config/permission.ts b/packages/opencode/src/config/permission.ts index a7390e953..29278338d 100644 --- a/packages/opencode/src/config/permission.ts +++ b/packages/opencode/src/config/permission.ts @@ -1,7 +1,6 @@ export * as ConfigPermission from "./permission" import { Schema, SchemaGetter } from "effect" -import z from "zod" -import { ZodOverride, zod } from "@/util/effect-zod" +import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" export const Action = Schema.Literals(["ask", "allow", "deny"]) @@ -20,8 +19,8 @@ export const Rule = Schema.Union([Action, Object]) export type Rule = Schema.Schema.Type // Known permission keys get explicit types in the Effect schema for generated -// docs/types. Runtime config parsing uses `InfoZod` below so user key order is -// preserved for permission precedence. +// docs/types. Runtime config parsing uses Effect's `propertyOrder: "original"` +// parse option so user key order is preserved for permission precedence. const InputObject = Schema.StructWithRest( Schema.Struct({ read: Schema.optional(Rule), @@ -53,35 +52,6 @@ const InputSchema = Schema.Union([Action, InputObject]) const normalizeInput = (input: Schema.Schema.Type): Schema.Schema.Type => typeof input === "string" ? { "*": input } : input -const InfoZod = z - .union([ - zod(Action), - z.intersection( - z.record(z.string(), zod(Rule)), - z - .object({ - read: zod(Rule).optional(), - edit: zod(Rule).optional(), - glob: zod(Rule).optional(), - grep: zod(Rule).optional(), - list: zod(Rule).optional(), - bash: zod(Rule).optional(), - task: zod(Rule).optional(), - external_directory: zod(Rule).optional(), - todowrite: zod(Action).optional(), - question: zod(Action).optional(), - webfetch: zod(Action).optional(), - websearch: zod(Action).optional(), - codesearch: zod(Action).optional(), - lsp: zod(Rule).optional(), - doom_loop: zod(Action).optional(), - skill: zod(Rule).optional(), - }) - .catchall(zod(Rule)), - ), - ]) - .transform(normalizeInput) - export const Info = InputSchema.pipe( Schema.decodeTo(InputObject, { decode: SchemaGetter.transform(normalizeInput), @@ -92,7 +62,6 @@ export const Info = InputSchema.pipe( }), ) .annotate({ identifier: "PermissionConfig" }) - .annotate({ [ZodOverride]: InfoZod }) .pipe( // Walker already emits the decodeTo transform into the derived zod (see // `encoded()` in effect-zod.ts), so just expose that directly. diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 56b8e7acd..3b75e1501 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -645,6 +645,33 @@ Test agent prompt`, }) }) +test("agent markdown permission config preserves user key order", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + const agentDir = path.join(dir, ".opencode", "agent") + await fs.mkdir(agentDir, { recursive: true }) + + await Filesystem.write( + path.join(agentDir, "ordered.md"), + `--- +permission: + bash: allow + "*": deny + edit: ask +--- +Ordered permissions`, + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await load() + expect(Object.keys(config.agent?.ordered?.permission ?? {})).toEqual(["bash", "*", "edit"]) + }, + }) +}) + test("loads agents from .opencode/agents (plural)", async () => { await using tmp = await tmpdir({ init: async (dir) => { @@ -1540,6 +1567,29 @@ test("permission config preserves user key order", async () => { }) }) +test("Effect config parser preserves permission order while rejecting unknown top-level keys", () => { + const config = ConfigParse.effectSchema( + Config.Info, + { + permission: { + bash: "allow", + "*": "deny", + edit: "ask", + }, + }, + "test", + ) + + expect(Object.keys(config.permission!)).toEqual(["bash", "*", "edit"]) + try { + ConfigParse.effectSchema(Config.Info, { invalid_field: true }, "test") + throw new Error("expected config parse to fail") + } catch (err) { + const error = err as { data?: { issues?: Array<{ code?: string; keys?: string[]; path?: string[] }> } } + expect(error.data?.issues?.[0]).toMatchObject({ code: "unrecognized_keys", keys: ["invalid_field"], path: [] }) + } +}) + // MCP config merging tests test("project config can override MCP server enabled status", async () => { @@ -2222,8 +2272,8 @@ describe("OPENCODE_CONFIG_CONTENT token substitution", () => { // parseManagedPlist unit tests — pure function, no OS interaction test("parseManagedPlist strips MDM metadata keys", async () => { - const config = ConfigParse.schema( - Config.Info.zod, + const config = ConfigParse.effectSchema( + Config.Info, ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ @@ -2250,8 +2300,8 @@ test("parseManagedPlist strips MDM metadata keys", async () => { }) test("parseManagedPlist parses server settings", async () => { - const config = ConfigParse.schema( - Config.Info.zod, + const config = ConfigParse.effectSchema( + Config.Info, ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ @@ -2270,8 +2320,8 @@ test("parseManagedPlist parses server settings", async () => { }) test("parseManagedPlist parses permission rules", async () => { - const config = ConfigParse.schema( - Config.Info.zod, + const config = ConfigParse.effectSchema( + Config.Info, ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ @@ -2300,8 +2350,8 @@ test("parseManagedPlist parses permission rules", async () => { }) test("parseManagedPlist parses enabled_providers", async () => { - const config = ConfigParse.schema( - Config.Info.zod, + const config = ConfigParse.effectSchema( + Config.Info, ConfigParse.jsonc( await ConfigManaged.parseManagedPlist( JSON.stringify({ @@ -2317,8 +2367,8 @@ test("parseManagedPlist parses enabled_providers", async () => { }) test("parseManagedPlist handles empty config", async () => { - const config = ConfigParse.schema( - Config.Info.zod, + const config = ConfigParse.effectSchema( + Config.Info, ConfigParse.jsonc( await ConfigManaged.parseManagedPlist(JSON.stringify({ $schema: "https://opencode.ai/config.json" })), "test:mobileconfig", From 1a734adb4d1ce6071432bd68ac45fa4457f0dc2e Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 25 Apr 2026 13:29:52 -0400 Subject: [PATCH 162/426] core: consolidate shared infrastructure into core package Moves effect logging, observability, runtime utilities, flags, installation version info, and process utilities from opencode to core package. This enables better code sharing across packages and establishes core as the single source of truth for foundational utilities. All internal imports updated to use @opencode-ai/core paths for consistency. --- bun.lock | 5 +++ packages/core/package.json | 5 +++ .../{opencode => core}/src/effect/logger.ts | 2 +- .../{opencode => core}/src/effect/memo-map.ts | 0 .../src/effect/observability.ts | 8 ++-- .../{opencode => core}/src/effect/runtime.ts | 6 ++- packages/{opencode => core}/src/flag/flag.ts | 0 packages/core/src/global.ts | 41 +++++++++++-------- .../src/installation/version.ts | 0 packages/{opencode => core}/src/util/log.ts | 4 +- .../src/util/opencode-process.ts | 0 .../test/effect/observability.test.ts | 2 +- packages/opencode/src/acp/agent.ts | 2 +- packages/opencode/src/cli/cmd/mcp.ts | 2 +- packages/opencode/src/cli/cmd/run.ts | 2 +- packages/opencode/src/cli/cmd/serve.ts | 2 +- packages/opencode/src/cli/cmd/session.ts | 2 +- packages/opencode/src/cli/cmd/tui/app.tsx | 2 +- .../cmd/tui/component/dialog-session-list.tsx | 2 +- .../tui/component/dialog-workspace-create.tsx | 2 +- .../cli/cmd/tui/component/error-component.tsx | 2 +- .../src/cli/cmd/tui/config/tui-migrate.ts | 2 +- .../opencode/src/cli/cmd/tui/config/tui.ts | 6 +-- .../opencode/src/cli/cmd/tui/context/sdk.tsx | 2 +- packages/opencode/src/cli/cmd/tui/layer.ts | 2 +- .../opencode/src/cli/cmd/tui/plugin/api.tsx | 2 +- .../src/cli/cmd/tui/plugin/runtime.ts | 2 +- .../src/cli/cmd/tui/routes/session/index.tsx | 2 +- .../cli/cmd/tui/routes/session/sidebar.tsx | 2 +- packages/opencode/src/cli/cmd/tui/thread.ts | 2 +- .../opencode/src/cli/cmd/tui/ui/dialog.tsx | 2 +- packages/opencode/src/cli/cmd/tui/worker.ts | 4 +- packages/opencode/src/cli/cmd/upgrade.ts | 2 +- packages/opencode/src/cli/cmd/web.ts | 2 +- packages/opencode/src/cli/heap.ts | 2 +- packages/opencode/src/cli/upgrade.ts | 4 +- packages/opencode/src/config/config.ts | 4 +- packages/opencode/src/config/paths.ts | 2 +- .../opencode/src/control-plane/workspace.ts | 2 +- packages/opencode/src/effect/app-runtime.ts | 4 +- .../opencode/src/effect/bootstrap-runtime.ts | 4 +- packages/opencode/src/effect/index.ts | 4 +- .../opencode/src/effect/instance-state.ts | 2 +- packages/opencode/src/effect/run-service.ts | 4 +- packages/opencode/src/file/ripgrep.ts | 2 +- packages/opencode/src/file/watcher.ts | 2 +- packages/opencode/src/format/formatter.ts | 2 +- packages/opencode/src/index.ts | 4 +- packages/opencode/src/installation/index.ts | 4 +- packages/opencode/src/lsp/lsp.ts | 2 +- packages/opencode/src/lsp/server.ts | 2 +- packages/opencode/src/mcp/index.ts | 2 +- packages/opencode/src/npm/index.ts | 2 +- packages/opencode/src/plugin/codex.ts | 2 +- .../src/plugin/github-copilot/copilot.ts | 2 +- packages/opencode/src/plugin/index.ts | 2 +- packages/opencode/src/plugin/loader.ts | 2 +- packages/opencode/src/plugin/meta.ts | 2 +- packages/opencode/src/project/project.ts | 2 +- packages/opencode/src/provider/models.ts | 2 +- packages/opencode/src/provider/provider.ts | 4 +- packages/opencode/src/provider/transform.ts | 2 +- packages/opencode/src/server/middleware.ts | 2 +- packages/opencode/src/server/routes/global.ts | 2 +- .../server/routes/instance/httpapi/auth.ts | 2 +- .../server/routes/instance/httpapi/server.ts | 2 +- .../src/server/routes/instance/index.ts | 2 +- packages/opencode/src/server/routes/ui.ts | 2 +- packages/opencode/src/server/server.ts | 2 +- packages/opencode/src/server/workspace.ts | 2 +- packages/opencode/src/session/instruction.ts | 2 +- packages/opencode/src/session/llm.ts | 4 +- packages/opencode/src/session/prompt.ts | 2 +- packages/opencode/src/session/session.ts | 4 +- packages/opencode/src/share/session.ts | 2 +- packages/opencode/src/shell/shell.ts | 2 +- packages/opencode/src/skill/index.ts | 2 +- packages/opencode/src/storage/db.ts | 4 +- packages/opencode/src/sync/index.ts | 2 +- packages/opencode/src/temporary.ts | 2 +- packages/opencode/src/tool/bash.ts | 2 +- packages/opencode/src/tool/registry.ts | 2 +- packages/opencode/src/util/index.ts | 2 +- .../test/installation/installation.test.ts | 2 +- .../test/plugin/workspace-adaptor.test.ts | 2 +- .../test/server/httpapi-bridge.test.ts | 2 +- .../test/server/httpapi-instance.test.ts | 2 +- packages/opencode/test/storage/db.test.ts | 2 +- packages/opencode/test/sync/index.test.ts | 2 +- .../test/workspace/workspace-restore.test.ts | 2 +- 90 files changed, 140 insertions(+), 119 deletions(-) rename packages/{opencode => core}/src/effect/logger.ts (98%) rename packages/{opencode => core}/src/effect/memo-map.ts (100%) rename packages/{opencode => core}/src/effect/observability.ts (92%) rename packages/{opencode => core}/src/effect/runtime.ts (94%) rename packages/{opencode => core}/src/flag/flag.ts (100%) rename packages/{opencode => core}/src/installation/version.ts (100%) rename packages/{opencode => core}/src/util/log.ts (98%) rename packages/{opencode => core}/src/util/opencode-process.ts (100%) rename packages/{opencode => core}/test/effect/observability.test.ts (96%) diff --git a/bun.lock b/bun.lock index e28376682..2420ab6df 100644 --- a/bun.lock +++ b/bun.lock @@ -197,8 +197,13 @@ "opencode": "./bin/opencode", }, "dependencies": { + "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", "@npmcli/arborist": "catalog:", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/context-async-hooks": "2.6.1", + "@opentelemetry/exporter-trace-otlp-http": "0.214.0", + "@opentelemetry/sdk-trace-base": "2.6.1", "effect": "catalog:", "glob": "13.0.5", "mime-types": "3.0.2", diff --git a/packages/core/package.json b/packages/core/package.json index 48d44ccf3..a244ea8b4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -23,8 +23,13 @@ "@types/npmcli__arborist": "6.3.3" }, "dependencies": { + "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", "@npmcli/arborist": "catalog:", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/context-async-hooks": "2.6.1", + "@opentelemetry/exporter-trace-otlp-http": "0.214.0", + "@opentelemetry/sdk-trace-base": "2.6.1", "effect": "catalog:", "glob": "13.0.5", "mime-types": "3.0.2", diff --git a/packages/opencode/src/effect/logger.ts b/packages/core/src/effect/logger.ts similarity index 98% rename from packages/opencode/src/effect/logger.ts rename to packages/core/src/effect/logger.ts index 0e58b8acb..69f9631e0 100644 --- a/packages/opencode/src/effect/logger.ts +++ b/packages/core/src/effect/logger.ts @@ -1,5 +1,5 @@ import { Cause, Effect, Logger, References } from "effect" -import { Log } from "@/util" +import * as Log from "../util/log" type Fields = Record diff --git a/packages/opencode/src/effect/memo-map.ts b/packages/core/src/effect/memo-map.ts similarity index 100% rename from packages/opencode/src/effect/memo-map.ts rename to packages/core/src/effect/memo-map.ts diff --git a/packages/opencode/src/effect/observability.ts b/packages/core/src/effect/observability.ts similarity index 92% rename from packages/opencode/src/effect/observability.ts rename to packages/core/src/effect/observability.ts index fb81d5f5b..0203079ab 100644 --- a/packages/opencode/src/effect/observability.ts +++ b/packages/core/src/effect/observability.ts @@ -2,9 +2,9 @@ import { Effect, Layer, Logger } from "effect" import { FetchHttpClient } from "effect/unstable/http" import { OtlpLogger, OtlpSerialization } from "effect/unstable/observability" import * as EffectLogger from "./logger" -import { Flag } from "@/flag/flag" -import { InstallationChannel, InstallationVersion } from "@/installation/version" -import { ensureProcessMetadata } from "@/util/opencode-process" +import { Flag } from "../flag/flag" +import { InstallationChannel, InstallationVersion } from "../installation/version" +import { ensureProcessMetadata } from "../util/opencode-process" const base = Flag.OTEL_EXPORTER_OTLP_ENDPOINT export const enabled = !!base @@ -76,7 +76,7 @@ const traces = async () => { // register(), so the global @opentelemetry/api context manager stays // as the no-op default. Non-Effect code (like the AI SDK) that calls // tracer.startActiveSpan() relies on context.active() to find the - // parent span — without a real context manager every span starts a + // parent span - without a real context manager every span starts a // new trace. Registering AsyncLocalStorageContextManager fixes this. const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks") const { context } = await import("@opentelemetry/api") diff --git a/packages/opencode/src/effect/runtime.ts b/packages/core/src/effect/runtime.ts similarity index 94% rename from packages/opencode/src/effect/runtime.ts rename to packages/core/src/effect/runtime.ts index ad7872f0b..e4f682709 100644 --- a/packages/opencode/src/effect/runtime.ts +++ b/packages/core/src/effect/runtime.ts @@ -1,11 +1,13 @@ -import { Observability } from "./observability" import { Layer, type Context, ManagedRuntime, type Effect } from "effect" import { memoMap } from "./memo-map" +import { Observability } from "./observability" export function makeRuntime(service: Context.Service, layer: Layer.Layer) { let rt: ManagedRuntime.ManagedRuntime | undefined const getRuntime = () => - (rt ??= ManagedRuntime.make(Layer.provideMerge(layer, Observability.layer) as Layer.Layer, { memoMap })) + (rt ??= ManagedRuntime.make(Layer.provideMerge(layer, Observability.layer) as Layer.Layer, { + memoMap, + })) return { runSync: (fn: (svc: S) => Effect.Effect) => getRuntime().runSync(service.use(fn)), diff --git a/packages/opencode/src/flag/flag.ts b/packages/core/src/flag/flag.ts similarity index 100% rename from packages/opencode/src/flag/flag.ts rename to packages/core/src/flag/flag.ts diff --git a/packages/core/src/global.ts b/packages/core/src/global.ts index 538cc091b..bf605618f 100644 --- a/packages/core/src/global.ts +++ b/packages/core/src/global.ts @@ -3,6 +3,24 @@ import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir" import os from "os" import { Context, Effect, Layer } from "effect" +const app = "opencode" +const data = path.join(xdgData!, app) +const cache = path.join(xdgCache!, app) +const config = path.join(xdgConfig!, app) +const state = path.join(xdgState!, app) + +export const Path = { + get home() { + return process.env.OPENCODE_TEST_HOME ?? os.homedir() + }, + data, + bin: path.join(cache, "bin"), + log: path.join(data, "log"), + cache, + config, + state, +} + export namespace Global { export class Service extends Context.Service()("@opencode/Global") {} @@ -19,23 +37,14 @@ export namespace Global { export const layer = Layer.effect( Service, Effect.gen(function* () { - const app = "opencode" - const home = process.env.OPENCODE_TEST_HOME ?? os.homedir() - const data = path.join(xdgData!, app) - const cache = path.join(xdgCache!, app) - const cfg = path.join(xdgConfig!, app) - const state = path.join(xdgState!, app) - const bin = path.join(cache, "bin") - const log = path.join(data, "log") - return Service.of({ - home, - data, - cache, - config: cfg, - state, - bin, - log, + home: Path.home, + data: Path.data, + cache: Path.cache, + config: Path.config, + state: Path.state, + bin: Path.bin, + log: Path.log, }) }), ) diff --git a/packages/opencode/src/installation/version.ts b/packages/core/src/installation/version.ts similarity index 100% rename from packages/opencode/src/installation/version.ts rename to packages/core/src/installation/version.ts diff --git a/packages/opencode/src/util/log.ts b/packages/core/src/util/log.ts similarity index 98% rename from packages/opencode/src/util/log.ts rename to packages/core/src/util/log.ts index e335a8b43..a61c15f7a 100644 --- a/packages/opencode/src/util/log.ts +++ b/packages/core/src/util/log.ts @@ -1,9 +1,9 @@ import path from "path" import fs from "fs/promises" import { createWriteStream } from "fs" -import { Global } from "../global" +import * as Global from "../global" import z from "zod" -import { Glob } from "@opencode-ai/core/util/glob" +import { Glob } from "./glob" export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" }) export type Level = z.infer diff --git a/packages/opencode/src/util/opencode-process.ts b/packages/core/src/util/opencode-process.ts similarity index 100% rename from packages/opencode/src/util/opencode-process.ts rename to packages/core/src/util/opencode-process.ts diff --git a/packages/opencode/test/effect/observability.test.ts b/packages/core/test/effect/observability.test.ts similarity index 96% rename from packages/opencode/test/effect/observability.test.ts rename to packages/core/test/effect/observability.test.ts index d06220282..50ea23f89 100644 --- a/packages/opencode/test/effect/observability.test.ts +++ b/packages/core/test/effect/observability.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, test } from "bun:test" -import { resource } from "../../src/effect/observability" +import { resource } from "@opencode-ai/core/effect/observability" const otelResourceAttributes = process.env.OTEL_RESOURCE_ATTRIBUTES const opencodeClient = process.env.OPENCODE_CLIENT diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 6ab24e26b..aff523a7e 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -50,7 +50,7 @@ import { Result, Schema } from "effect" import { LoadAPIKeyError } from "ai" import type { AssistantMessage, Event, OpencodeClient, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2" import { applyPatch } from "diff" -import { InstallationVersion } from "@/installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" type ModeOption = { id: string; name: string; description?: string } type ModelOption = { modelId: string; name: string } diff --git a/packages/opencode/src/cli/cmd/mcp.ts b/packages/opencode/src/cli/cmd/mcp.ts index a5751ce83..3269b4a3d 100644 --- a/packages/opencode/src/cli/cmd/mcp.ts +++ b/packages/opencode/src/cli/cmd/mcp.ts @@ -11,7 +11,7 @@ import { Config } from "../../config" import { ConfigMCP } from "../../config/mcp" import { Instance } from "../../project/instance" import { Installation } from "../../installation" -import { InstallationVersion } from "../../installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" import path from "path" import { Global } from "../../global" import { modify, applyEdits } from "jsonc-parser" diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 0874beee1..a9e044f18 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -3,7 +3,7 @@ import path from "path" import { pathToFileURL } from "url" import { UI } from "../ui" import { cmd } from "./cmd" -import { Flag } from "../../flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { bootstrap } from "../bootstrap" import { EOL } from "os" import { Filesystem } from "../../util" diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index d5eee75dd..5f3211aa1 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -1,7 +1,7 @@ import { Server } from "../../server/server" import { cmd } from "./cmd" import { withNetworkOptions, resolveNetworkOptions } from "../network" -import { Flag } from "../../flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" export const ServeCommand = cmd({ command: "serve", diff --git a/packages/opencode/src/cli/cmd/session.ts b/packages/opencode/src/cli/cmd/session.ts index 8537a74d4..0d4bd96b0 100644 --- a/packages/opencode/src/cli/cmd/session.ts +++ b/packages/opencode/src/cli/cmd/session.ts @@ -5,7 +5,7 @@ import { SessionID } from "../../session/schema" import { bootstrap } from "../bootstrap" import { UI } from "../ui" import { Locale } from "../../util" -import { Flag } from "../../flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Filesystem } from "../../util" import { Process } from "../../util" import { EOL } from "os" diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 30a597b91..015b0ed8f 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -16,7 +16,7 @@ import { on, } from "solid-js" import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import semver from "semver" import { DialogProvider, useDialog } from "@tui/ui/dialog" import { DialogProvider as DialogProviderList } from "@tui/component/dialog-provider" diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 32342e772..7260a14f9 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -8,7 +8,7 @@ import { useProject } from "@tui/context/project" import { useKeybind } from "../context/keybind" import { useTheme } from "../context/theme" import { useSDK } from "../context/sdk" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { DialogSessionRename } from "./dialog-session-rename" import { Keybind } from "@/util" import { createDebouncedSignal } from "../util/signal" diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx index a16c98a9f..899ab42ee 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx @@ -7,7 +7,7 @@ import { useProject } from "@tui/context/project" import { createMemo, createSignal, onMount } from "solid-js" import { setTimeout as sleep } from "node:timers/promises" import { errorData, errorMessage } from "@/util/error" -import * as Log from "@/util/log" +import * as Log from "@opencode-ai/core/util/log" import { useSDK } from "../context/sdk" import { useToast } from "../ui/toast" diff --git a/packages/opencode/src/cli/cmd/tui/component/error-component.tsx b/packages/opencode/src/cli/cmd/tui/component/error-component.tsx index c74d3bbc6..fcbd27ca9 100644 --- a/packages/opencode/src/cli/cmd/tui/component/error-component.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/error-component.tsx @@ -2,7 +2,7 @@ import { TextAttributes } from "@opentui/core" import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid" import * as Clipboard from "@tui/util/clipboard" import { createSignal } from "solid-js" -import { InstallationVersion } from "@/installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" import { win32FlushInputBuffer } from "../win32" import { getScrollAcceleration } from "../util/scroll" diff --git a/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts b/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts index a7f50ddf9..d5599c170 100644 --- a/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts +++ b/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts @@ -3,7 +3,7 @@ import { type ParseError as JsoncParseError, applyEdits, modify, parse as parseJ import { unique } from "remeda" import z from "zod" import { TuiInfo, TuiOptions } from "./tui-schema" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@/global" import { Filesystem, Log } from "@/util" import * as ConfigPaths from "@/config/paths" diff --git a/packages/opencode/src/cli/cmd/tui/config/tui.ts b/packages/opencode/src/cli/cmd/tui/config/tui.ts index 8dc6ab07e..64ec5f1c5 100644 --- a/packages/opencode/src/cli/cmd/tui/config/tui.ts +++ b/packages/opencode/src/cli/cmd/tui/config/tui.ts @@ -7,15 +7,15 @@ import { ConfigParse } from "@/config/parse" import * as ConfigPaths from "@/config/paths" import { migrateTuiConfig } from "./tui-migrate" import { TuiInfo } from "./tui-schema" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { isRecord } from "@/util/record" import { Global } from "@/global" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { CurrentWorkingDirectory } from "./cwd" import { ConfigPlugin } from "@/config/plugin" import { ConfigKeybinds } from "@/config/keybinds" -import { InstallationLocal, InstallationVersion } from "@/installation/version" -import { makeRuntime } from "@/effect/runtime" +import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version" +import { makeRuntime } from "@opencode-ai/core/effect/runtime" import { Filesystem, Log } from "@/util" import { ConfigVariable } from "@/config/variable" import { Npm } from "@/npm" diff --git a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx index 6a240ceef..96fa54487 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sdk.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sdk.tsx @@ -2,7 +2,7 @@ import { createOpencodeClient } from "@opencode-ai/sdk/v2" import type { GlobalEvent } from "@opencode-ai/sdk/v2" import { createSimpleContext } from "./helper" import { createGlobalEmitter } from "@solid-primitives/event-bus" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { batch, onCleanup, onMount } from "solid-js" export type EventSource = { diff --git a/packages/opencode/src/cli/cmd/tui/layer.ts b/packages/opencode/src/cli/cmd/tui/layer.ts index 64cba08e8..785455334 100644 --- a/packages/opencode/src/cli/cmd/tui/layer.ts +++ b/packages/opencode/src/cli/cmd/tui/layer.ts @@ -1,6 +1,6 @@ import { Layer } from "effect" import { TuiConfig } from "./config/tui" import { Npm } from "@/npm" -import { Observability } from "@/effect/observability" +import { Observability } from "@opencode-ai/core/effect/observability" export const CliLayer = Observability.layer.pipe(Layer.merge(TuiConfig.layer), Layer.provide(Npm.defaultLayer)) diff --git a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx index 5bea48380..25ea3ac9e 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx +++ b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx @@ -18,7 +18,7 @@ import { DialogSelect, type DialogSelectOption as SelectOption } from "../ui/dia import { Prompt } from "../component/prompt" import { Slot as HostSlot } from "./slots" import type { useToast } from "../ui/toast" -import { InstallationVersion } from "@/installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" type RouteEntry = { key: symbol diff --git a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts index 8eda7e022..95d050d7f 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts @@ -33,7 +33,7 @@ import { Global } from "@/global" import { Filesystem } from "@/util" import { Process } from "@/util" import { Flock } from "@opencode-ai/core/util/flock" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { INTERNAL_TUI_PLUGINS, type InternalTuiPlugin } from "./internal" import { setupSlots, Slot as View } from "./slots" import type { HostPluginApi, HostSlots } from "./slots" 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 c04e58ace..6ba43deb9 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -64,7 +64,7 @@ import { DialogForkFromTimeline } from "./dialog-fork-from-timeline" import { DialogSessionRename } from "../../component/dialog-session-rename" import { Sidebar } from "./sidebar" import { SubagentFooter } from "./subagent-footer.tsx" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import parsers from "../../../../../../parsers-config.ts" import * as Clipboard from "../../util/clipboard" diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx index 6d92752ef..c49946df7 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx @@ -3,7 +3,7 @@ import { useSync } from "@tui/context/sync" import { createMemo, Show } from "solid-js" import { useTheme } from "../../context/theme" import { useTuiConfig } from "../../context/tui-config" -import { InstallationChannel, InstallationVersion } from "@/installation/version" +import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version" import { TuiPluginRuntime } from "../../plugin" import { getScrollAcceleration } from "../../util/scroll" diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index a2a53ecaf..60c5d5ece 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -15,7 +15,7 @@ import type { EventSource } from "./context/sdk" import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32" import { writeHeapSnapshot } from "v8" import { TuiConfig } from "./config/tui" -import { OPENCODE_PROCESS_ROLE, OPENCODE_RUN_ID, ensureRunID, sanitizedProcessEnv } from "@/util/opencode-process" +import { OPENCODE_PROCESS_ROLE, OPENCODE_RUN_ID, ensureRunID, sanitizedProcessEnv } from "@opencode-ai/core/util/opencode-process" import { validateSession } from "./validate-session" declare global { diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx index 29eb6fd4c..a5da735f6 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog.tsx @@ -4,7 +4,7 @@ import { useTheme } from "@tui/context/theme" import { MouseButton, Renderable, RGBA } from "@opentui/core" import { createStore } from "solid-js/store" import { useToast } from "./toast" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import * as Selection from "@tui/util/selection" export function Dialog( diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index 8cec99c61..df09d5cc9 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -7,11 +7,11 @@ import { Rpc } from "@/util" import { upgrade } from "@/cli/upgrade" import { Config } from "@/config" import { GlobalBus } from "@/bus/global" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { writeHeapSnapshot } from "node:v8" import { Heap } from "@/cli/heap" import { AppRuntime } from "@/effect/app-runtime" -import { ensureProcessMetadata } from "@/util/opencode-process" +import { ensureProcessMetadata } from "@opencode-ai/core/util/opencode-process" ensureProcessMetadata("worker") diff --git a/packages/opencode/src/cli/cmd/upgrade.ts b/packages/opencode/src/cli/cmd/upgrade.ts index b80648c24..a60b1fb0b 100644 --- a/packages/opencode/src/cli/cmd/upgrade.ts +++ b/packages/opencode/src/cli/cmd/upgrade.ts @@ -3,7 +3,7 @@ import { UI } from "../ui" import * as prompts from "@clack/prompts" import { AppRuntime } from "@/effect/app-runtime" import { Installation } from "../../installation" -import { InstallationVersion } from "../../installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" export const UpgradeCommand = { command: "upgrade [target]", diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts index 9dd8796d6..19ee38ff5 100644 --- a/packages/opencode/src/cli/cmd/web.ts +++ b/packages/opencode/src/cli/cmd/web.ts @@ -2,7 +2,7 @@ import { Server } from "../../server/server" import { UI } from "../ui" import { cmd } from "./cmd" import { withNetworkOptions, resolveNetworkOptions } from "../network" -import { Flag } from "../../flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import open from "open" import { networkInterfaces } from "os" diff --git a/packages/opencode/src/cli/heap.ts b/packages/opencode/src/cli/heap.ts index 87b7b2ebf..0cb4299c5 100644 --- a/packages/opencode/src/cli/heap.ts +++ b/packages/opencode/src/cli/heap.ts @@ -1,6 +1,6 @@ import path from "path" import { writeHeapSnapshot } from "node:v8" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@/global" import { Log } from "@/util" diff --git a/packages/opencode/src/cli/upgrade.ts b/packages/opencode/src/cli/upgrade.ts index a3e3f3013..da0451c55 100644 --- a/packages/opencode/src/cli/upgrade.ts +++ b/packages/opencode/src/cli/upgrade.ts @@ -1,9 +1,9 @@ import { Bus } from "@/bus" import { Config } from "@/config" import { AppRuntime } from "@/effect/app-runtime" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Installation } from "@/installation" -import { InstallationVersion } from "@/installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" export async function upgrade() { const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.getGlobal())) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 70ba14464..3958e1436 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -7,12 +7,12 @@ import { mergeDeep, pipe } from "remeda" import { Global } from "../global" import fsNode from "fs/promises" import { NamedError } from "@opencode-ai/core/util/error" -import { Flag } from "../flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Auth } from "../auth" import { Env } from "../env" import { applyEdits, modify } from "jsonc-parser" import { Instance, type InstanceContext } from "../project/instance" -import { InstallationLocal, InstallationVersion } from "@/installation/version" +import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version" import { existsSync } from "fs" import { GlobalBus } from "@/bus/global" import { Event } from "../server/event" diff --git a/packages/opencode/src/config/paths.ts b/packages/opencode/src/config/paths.ts index 572676fcc..df98bebb2 100644 --- a/packages/opencode/src/config/paths.ts +++ b/packages/opencode/src/config/paths.ts @@ -2,7 +2,7 @@ export * as ConfigPaths from "./paths" import path from "path" import { Filesystem } from "@/util" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@/global" import { unique } from "remeda" import { JsonError } from "./error" diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index e1ebb613e..fbc4336fe 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -8,7 +8,7 @@ import { GlobalBus } from "@/bus/global" import { Auth } from "@/auth" import { SyncEvent } from "@/sync" import { EventSequenceTable, EventTable } from "@/sync/event.sql" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Log } from "@/util" import { Filesystem } from "@/util" import { ProjectID } from "@/project/schema" diff --git a/packages/opencode/src/effect/app-runtime.ts b/packages/opencode/src/effect/app-runtime.ts index 6c9d949b8..b4bdbfca4 100644 --- a/packages/opencode/src/effect/app-runtime.ts +++ b/packages/opencode/src/effect/app-runtime.ts @@ -1,6 +1,6 @@ import { Layer, ManagedRuntime } from "effect" import { attach } from "./run-service" -import * as Observability from "./observability" +import * as Observability from "@opencode-ai/core/effect/observability" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Bus } from "@/bus" @@ -47,7 +47,7 @@ import { Installation } from "@/installation" import { ShareNext } from "@/share" import { SessionShare } from "@/share" import { Npm } from "@/npm" -import { memoMap } from "./memo-map" +import { memoMap } from "@opencode-ai/core/effect/memo-map" export const AppLayer = Layer.mergeAll( Npm.defaultLayer, diff --git a/packages/opencode/src/effect/bootstrap-runtime.ts b/packages/opencode/src/effect/bootstrap-runtime.ts index 37698c43a..2d542bf24 100644 --- a/packages/opencode/src/effect/bootstrap-runtime.ts +++ b/packages/opencode/src/effect/bootstrap-runtime.ts @@ -10,8 +10,8 @@ import { Vcs } from "@/project" import { Snapshot } from "@/snapshot" import { Bus } from "@/bus" import { Config } from "@/config" -import * as Observability from "./observability" -import { memoMap } from "./memo-map" +import * as Observability from "@opencode-ai/core/effect/observability" +import { memoMap } from "@opencode-ai/core/effect/memo-map" export const BootstrapLayer = Layer.mergeAll( Config.defaultLayer, diff --git a/packages/opencode/src/effect/index.ts b/packages/opencode/src/effect/index.ts index 410ce00c2..623bd5f0b 100644 --- a/packages/opencode/src/effect/index.ts +++ b/packages/opencode/src/effect/index.ts @@ -1,5 +1,5 @@ export * as InstanceState from "./instance-state" export * as EffectBridge from "./bridge" export * as Runner from "./runner" -export * as Observability from "./observability" -export * as EffectLogger from "./logger" +export * as Observability from "@opencode-ai/core/effect/observability" +export * as EffectLogger from "@opencode-ai/core/effect/logger" diff --git a/packages/opencode/src/effect/instance-state.ts b/packages/opencode/src/effect/instance-state.ts index 7095657f5..dc9214494 100644 --- a/packages/opencode/src/effect/instance-state.ts +++ b/packages/opencode/src/effect/instance-state.ts @@ -1,5 +1,5 @@ import { Effect, Fiber, ScopedCache, Scope, Context } from "effect" -import * as EffectLogger from "./logger" +import * as EffectLogger from "@opencode-ai/core/effect/logger" import { Instance, type InstanceContext } from "@/project/instance" import { LocalContext } from "@/util" import { InstanceRef, WorkspaceRef } from "./instance-ref" diff --git a/packages/opencode/src/effect/run-service.ts b/packages/opencode/src/effect/run-service.ts index 98ff83ea5..2a54979af 100644 --- a/packages/opencode/src/effect/run-service.ts +++ b/packages/opencode/src/effect/run-service.ts @@ -3,10 +3,10 @@ import * as Context from "effect/Context" import { Instance } from "@/project/instance" import { LocalContext } from "@/util" import { InstanceRef, WorkspaceRef } from "./instance-ref" -import * as Observability from "./observability" +import * as Observability from "@opencode-ai/core/effect/observability" import { WorkspaceContext } from "@/control-plane/workspace-context" import type { InstanceContext } from "@/project/instance" -import { memoMap } from "./memo-map" +import { memoMap } from "@opencode-ai/core/effect/memo-map" type Refs = { instance?: InstanceContext diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index e31f53733..5602a4c41 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -9,7 +9,7 @@ import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" import { Global } from "@/global" import { Log } from "@/util" -import { sanitizedProcessEnv } from "@/util/opencode-process" +import { sanitizedProcessEnv } from "@opencode-ai/core/util/opencode-process" import { which } from "@/util/which" import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts index 0ac98b9c2..57f3dda9f 100644 --- a/packages/opencode/src/file/watcher.ts +++ b/packages/opencode/src/file/watcher.ts @@ -8,7 +8,7 @@ import z from "zod" import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Git } from "@/git" import { Instance } from "@/project/instance" import { lazy } from "@/util/lazy" diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index 03f836527..eefafd575 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -3,7 +3,7 @@ import type { InstanceContext } from "../project/instance" import { Filesystem } from "../util" import { Process } from "../util" import { which } from "../util/which" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" export interface Context extends Pick {} diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index c27f6b740..3764e1b1c 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -11,7 +11,7 @@ import { UninstallCommand } from "./cli/cmd/uninstall" import { ModelsCommand } from "./cli/cmd/models" import { UI } from "./cli/ui" import { Installation } from "./installation" -import { InstallationVersion } from "./installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" import { NamedError } from "@opencode-ai/core/util/error" import { FormatError } from "./cli/error" import { ServeCommand } from "./cli/cmd/serve" @@ -38,7 +38,7 @@ import { errorMessage } from "./util/error" import { PluginCommand } from "./cli/cmd/plug" import { Heap } from "./cli/heap" import { drizzle } from "drizzle-orm/bun-sqlite" -import { ensureProcessMetadata } from "./util/opencode-process" +import { ensureProcessMetadata } from "@opencode-ai/core/util/opencode-process" const processMetadata = ensureProcessMetadata("main") diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index bb3de3f3b..1a39d3c61 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -6,11 +6,11 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import path from "path" import z from "zod" import { BusEvent } from "@/bus/bus-event" -import { Flag } from "../flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Log } from "../util" import semver from "semver" -import { InstallationChannel, InstallationVersion } from "./version" +import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version" const log = Log.create({ service: "installation" }) diff --git a/packages/opencode/src/lsp/lsp.ts b/packages/opencode/src/lsp/lsp.ts index 5078cbadb..96741b687 100644 --- a/packages/opencode/src/lsp/lsp.ts +++ b/packages/opencode/src/lsp/lsp.ts @@ -7,7 +7,7 @@ import { pathToFileURL, fileURLToPath } from "url" import * as LSPServer from "./server" import z from "zod" import { Config } from "../config" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Process } from "../util" import { spawn as lspspawn } from "./launch" import { Effect, Layer, Context, Schema } from "effect" diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index ef001888e..9b585c9fb 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -7,7 +7,7 @@ import { text } from "node:stream/consumers" import fs from "fs/promises" import { Filesystem } from "../util" import type { InstanceContext } from "../project/instance" -import { Flag } from "../flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Archive } from "../util" import { Process } from "../util" import { which } from "../util/which" diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 8b2562dc4..23862db63 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -15,7 +15,7 @@ import { Log } from "../util" import { NamedError } from "@opencode-ai/core/util/error" import z from "zod/v4" import { Installation } from "../installation" -import { InstallationVersion } from "../installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" import { withTimeout } from "@/util/timeout" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { McpOAuthProvider } from "./oauth-provider" diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index d876b0e52..ca67491d0 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -11,10 +11,10 @@ import { NodeFileSystem } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Global } from "@opencode-ai/core/global" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" +import { makeRuntime } from "@opencode-ai/core/effect/runtime" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import * as CrossSpawnSpawner from "../effect/cross-spawn-spawner" -import { makeRuntime } from "../effect/runtime" export class InstallFailedError extends Schema.TaggedErrorClass()("NpmInstallFailedError", { add: Schema.Array(Schema.String).pipe(Schema.optional), diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index 60d2d5b47..337a4e91f 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -1,7 +1,7 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin" import { Log } from "../util" import { Installation } from "../installation" -import { InstallationVersion } from "../installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" import { OAUTH_DUMMY_KEY } from "../auth" import os from "os" import { setTimeout as sleep } from "node:timers/promises" diff --git a/packages/opencode/src/plugin/github-copilot/copilot.ts b/packages/opencode/src/plugin/github-copilot/copilot.ts index 9b6f54459..6f0e46402 100644 --- a/packages/opencode/src/plugin/github-copilot/copilot.ts +++ b/packages/opencode/src/plugin/github-copilot/copilot.ts @@ -1,6 +1,6 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin" import type { Model } from "@opencode-ai/sdk/v2" -import { InstallationVersion } from "@/installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" import { iife } from "@/util/iife" import { Log } from "../../util" import { setTimeout as sleep } from "node:timers/promises" diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 4587d8fb1..762d38be3 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -9,7 +9,7 @@ import { Config } from "../config" import { Bus } from "../bus" import { Log } from "../util" import { createOpencodeClient } from "@opencode-ai/sdk" -import { Flag } from "../flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { CodexAuthPlugin } from "./codex" import { Session } from "../session" import { NamedError } from "@opencode-ai/core/util/error" diff --git a/packages/opencode/src/plugin/loader.ts b/packages/opencode/src/plugin/loader.ts index e61612561..f8da9d6a9 100644 --- a/packages/opencode/src/plugin/loader.ts +++ b/packages/opencode/src/plugin/loader.ts @@ -9,7 +9,7 @@ import { type PluginSource, } from "./shared" import { ConfigPlugin } from "@/config/plugin" -import { InstallationVersion } from "@/installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" export namespace PluginLoader { // A normalized plugin declaration derived from config before any filesystem or npm work happens. diff --git a/packages/opencode/src/plugin/meta.ts b/packages/opencode/src/plugin/meta.ts index 4c14a0dec..ab067c592 100644 --- a/packages/opencode/src/plugin/meta.ts +++ b/packages/opencode/src/plugin/meta.ts @@ -1,7 +1,7 @@ import path from "path" import { fileURLToPath } from "url" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@/global" import { Filesystem } from "@/util" import { Flock } from "@opencode-ai/core/util/flock" diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 88d033921..e622464b4 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -3,7 +3,7 @@ import { and, Database, eq } from "../storage" import { ProjectTable } from "./project.sql" import { SessionTable } from "../session/session.sql" import { Log } from "../util" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" import { which } from "../util/which" diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index e52464d6d..c3df06abc 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -3,7 +3,7 @@ import { Log } from "../util" import path from "path" import { Schema } from "effect" import { Installation } from "../installation" -import { Flag } from "../flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { lazy } from "@/util/lazy" import { Filesystem } from "../util" import { Flock } from "@opencode-ai/core/util/flock" diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index d6ccbacfc..96039af9b 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -11,8 +11,8 @@ import { type LanguageModelV3 } from "@ai-sdk/provider" import * as ModelsDev from "./models" import { Auth } from "../auth" import { Env } from "../env" -import { InstallationVersion } from "../installation/version" -import { Flag } from "../flag/flag" +import { InstallationVersion } from "@opencode-ai/core/installation/version" +import { Flag } from "@opencode-ai/core/flag/flag" import { zod } from "@/util/effect-zod" import { namedSchemaError } from "@/util/named-schema-error" import { iife } from "@/util/iife" diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 50529c4dd..67b02c089 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -5,7 +5,7 @@ import type { JSONSchema } from "zod/v4/core" import type * as Provider from "./provider" import type * as ModelsDev from "./models" import { iife } from "@/util/iife" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" type Modality = NonNullable["input"][number] diff --git a/packages/opencode/src/server/middleware.ts b/packages/opencode/src/server/middleware.ts index 55d9dee79..aceba8821 100644 --- a/packages/opencode/src/server/middleware.ts +++ b/packages/opencode/src/server/middleware.ts @@ -6,7 +6,7 @@ import type { ContentfulStatusCode } from "hono/utils/http-status" import type { ErrorHandler, MiddlewareHandler } from "hono" import { HTTPException } from "hono/http-exception" import { Log } from "../util" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { basicAuth } from "hono/basic-auth" import { cors } from "hono/cors" import { compress } from "hono/compress" diff --git a/packages/opencode/src/server/routes/global.ts b/packages/opencode/src/server/routes/global.ts index a1199a469..c2f8b695d 100644 --- a/packages/opencode/src/server/routes/global.ts +++ b/packages/opencode/src/server/routes/global.ts @@ -10,7 +10,7 @@ import { AppRuntime } from "@/effect/app-runtime" import { AsyncQueue } from "@/util/queue" import { Instance } from "../../project/instance" import { Installation } from "@/installation" -import { InstallationVersion } from "@/installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" import { Log } from "../../util" import { lazy } from "../../util/lazy" import { Config } from "../../config" diff --git a/packages/opencode/src/server/routes/instance/httpapi/auth.ts b/packages/opencode/src/server/routes/instance/httpapi/auth.ts index fe72b7822..2fe196b56 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/auth.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/auth.ts @@ -1,6 +1,6 @@ import { Effect, Encoding, Layer, Redacted, Schema } from "effect" import { HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" class Unauthorized extends Schema.TaggedErrorClass()( "Unauthorized", diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index 903cd103b..17c3ba4b4 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -18,7 +18,7 @@ import { ProjectApi, projectHandlers } from "./project" import { ProviderApi, providerHandlers } from "./provider" import { QuestionApi, questionHandlers } from "./question" import { WorkspaceApi, workspaceHandlers } from "./workspace" -import { memoMap } from "@/effect/memo-map" +import { memoMap } from "@opencode-ai/core/effect/memo-map" const Query = Schema.Struct({ directory: Schema.optional(Schema.String), diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index bc9d2b2ad..df50be406 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -14,7 +14,7 @@ import { LSP } from "@/lsp" import { Command } from "@/command" import { QuestionRoutes } from "./question" import { PermissionRoutes } from "./permission" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { ExperimentalHttpApiServer } from "./httpapi/server" import { FilePaths } from "./httpapi/file" import { InstancePaths } from "./httpapi/instance" diff --git a/packages/opencode/src/server/routes/ui.ts b/packages/opencode/src/server/routes/ui.ts index d449cd1c4..5e47e6bf7 100644 --- a/packages/opencode/src/server/routes/ui.ts +++ b/packages/opencode/src/server/routes/ui.ts @@ -1,4 +1,4 @@ -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Hono } from "hono" import { proxy } from "hono/proxy" import { getMimeType } from "hono/utils/mime" diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index d74de559d..fb278f268 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -3,7 +3,7 @@ import { Hono } from "hono" import { adapter } from "#hono" import { lazy } from "@/util/lazy" import { Log } from "@/util" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { WorkspaceID } from "@/control-plane/schema" import { MDNS } from "./mdns" import { AuthMiddleware, CompressionMiddleware, CorsMiddleware, ErrorMiddleware, LoggerMiddleware } from "./middleware" diff --git a/packages/opencode/src/server/workspace.ts b/packages/opencode/src/server/workspace.ts index d30a117d6..3f71bf2f7 100644 --- a/packages/opencode/src/server/workspace.ts +++ b/packages/opencode/src/server/workspace.ts @@ -4,7 +4,7 @@ import { getAdaptor } from "@/control-plane/adaptors" import { WorkspaceID } from "@/control-plane/schema" import { WorkspaceContext } from "@/control-plane/workspace-context" import { Workspace } from "@/control-plane/workspace" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { InstanceBootstrap } from "@/project/bootstrap" import { Instance } from "@/project/instance" import { Session } from "@/session" diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index a18a55584..56fca5359 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -4,7 +4,7 @@ import { Effect, Layer, Context } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http" import { Config } from "@/config" import { InstanceState } from "@/effect" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { withTransientReadRetry } from "@/util/effect-http-client" import { Global } from "../global" diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index b72f873de..d5ff4e61c 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -12,7 +12,7 @@ import type { Agent } from "@/agent/agent" import type { MessageV2 } from "./message-v2" import { Plugin } from "@/plugin" import { SystemPrompt } from "./system" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Permission } from "@/permission" import { PermissionID } from "@/permission/schema" import { Bus } from "@/bus" @@ -20,7 +20,7 @@ import { Wildcard } from "@/util" import { SessionID } from "@/session/schema" import { Auth } from "@/auth" import { Installation } from "@/installation" -import { InstallationVersion } from "@/installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" import { EffectBridge } from "@/effect" import * as Option from "effect/Option" import * as OtelTracer from "@effect/opentelemetry/Tracer" diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 8e227e602..708961168 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -24,7 +24,7 @@ import MAX_STEPS from "../session/prompt/max-steps.txt" import { ToolRegistry } from "../tool" import { MCP } from "../mcp" import { LSP } from "../lsp" -import { Flag } from "../flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { ulid } from "ulid" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 472339b05..db77c0e21 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -5,8 +5,8 @@ import { Bus } from "@/bus" import { Decimal } from "decimal.js" import z from "zod" import { type ProviderMetadata, type LanguageModelUsage } from "ai" -import { Flag } from "../flag/flag" -import { InstallationVersion } from "../installation/version" +import { Flag } from "@opencode-ai/core/flag/flag" +import { InstallationVersion } from "@opencode-ai/core/installation/version" import { Database, NotFoundError, eq, and, gte, isNull, desc, like, inArray, lt } from "../storage" import { SyncEvent } from "../sync" diff --git a/packages/opencode/src/share/session.ts b/packages/opencode/src/share/session.ts index 63b767078..c5394716b 100644 --- a/packages/opencode/src/share/session.ts +++ b/packages/opencode/src/share/session.ts @@ -3,7 +3,7 @@ import { SessionID } from "@/session/schema" import { SyncEvent } from "@/sync" import { Effect, Layer, Scope, Context } from "effect" import { Config } from "../config" -import { Flag } from "../flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import * as ShareNext from "./share-next" export interface Interface { diff --git a/packages/opencode/src/shell/shell.ts b/packages/opencode/src/shell/shell.ts index 60643c10b..1c8996194 100644 --- a/packages/opencode/src/shell/shell.ts +++ b/packages/opencode/src/shell/shell.ts @@ -1,4 +1,4 @@ -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { lazy } from "@/util/lazy" import { Filesystem } from "@/util" import { which } from "@/util/which" diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index e5282e250..a425e13d5 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -7,7 +7,7 @@ import { NamedError } from "@opencode-ai/core/util/error" import type { Agent } from "@/agent/agent" import { Bus } from "@/bus" import { InstanceState } from "@/effect" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Global } from "@/global" import { Permission } from "@/permission" import { AppFileSystem } from "@opencode-ai/core/filesystem" diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts index 67f5f1289..898810581 100644 --- a/packages/opencode/src/storage/db.ts +++ b/packages/opencode/src/storage/db.ts @@ -10,8 +10,8 @@ import { NamedError } from "@opencode-ai/core/util/error" import z from "zod" import path from "path" import { readFileSync, readdirSync, existsSync } from "fs" -import { Flag } from "../flag/flag" -import { InstallationChannel } from "../installation/version" +import { Flag } from "@opencode-ai/core/flag/flag" +import { InstallationChannel } from "@opencode-ai/core/installation/version" import { InstanceState } from "@/effect" import { iife } from "@/util/iife" import { init } from "#db" diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index 35a5abd0b..da33b7aa9 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -7,7 +7,7 @@ import { Instance } from "@/project/instance" import { EventSequenceTable, EventTable } from "./event.sql" import { WorkspaceContext } from "@/control-plane/workspace-context" import { EventID } from "./schema" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Schema as EffectSchema } from "effect" import { zodObject } from "@/util/effect-zod" import type { DeepMutable } from "@/util/schema" diff --git a/packages/opencode/src/temporary.ts b/packages/opencode/src/temporary.ts index bbb97e0f0..7747eb1e9 100644 --- a/packages/opencode/src/temporary.ts +++ b/packages/opencode/src/temporary.ts @@ -1,6 +1,6 @@ import yargs from "yargs" import { TuiThreadCommand } from "./cli/cmd/tui/thread" -import { InstallationVersion } from "./installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" import { hideBin } from "yargs/helpers" import { Log } from "./node" diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 1b8875326..eeba5ebd6 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -11,7 +11,7 @@ import { Language, type Node } from "web-tree-sitter" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { fileURLToPath } from "url" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Shell } from "@/shell/shell" import { BashArity } from "@/permission/arity" diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 629c57965..b7fa696c8 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -23,7 +23,7 @@ import { Provider } from "../provider" import { ProviderID, type ModelID } from "../provider/schema" import { WebSearchTool } from "./websearch" import { CodeSearchTool } from "./codesearch" -import { Flag } from "@/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Log } from "@/util" import { LspTool } from "./lsp" import * as Truncate from "./truncate" diff --git a/packages/opencode/src/util/index.ts b/packages/opencode/src/util/index.ts index f051ad964..c67a3d140 100644 --- a/packages/opencode/src/util/index.ts +++ b/packages/opencode/src/util/index.ts @@ -5,7 +5,7 @@ export * as Keybind from "./keybind" export * as LocalContext from "./local-context" export * as Locale from "./locale" export * as Lock from "./lock" -export * as Log from "./log" +export * as Log from "@opencode-ai/core/util/log" export * as Process from "./process" export * as Rpc from "./rpc" export * as Token from "./token" diff --git a/packages/opencode/test/installation/installation.test.ts b/packages/opencode/test/installation/installation.test.ts index 0d3e92989..469ebb714 100644 --- a/packages/opencode/test/installation/installation.test.ts +++ b/packages/opencode/test/installation/installation.test.ts @@ -3,7 +3,7 @@ import { Effect, Layer, Stream } from "effect" import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { Installation } from "../../src/installation" -import { InstallationChannel } from "../../src/installation/version" +import { InstallationChannel } from "@opencode-ai/core/installation/version" const encoder = new TextEncoder() diff --git a/packages/opencode/test/plugin/workspace-adaptor.test.ts b/packages/opencode/test/plugin/workspace-adaptor.test.ts index e74522c8b..2695e9b28 100644 --- a/packages/opencode/test/plugin/workspace-adaptor.test.ts +++ b/packages/opencode/test/plugin/workspace-adaptor.test.ts @@ -7,7 +7,7 @@ import { tmpdir } from "../fixture/fixture" const disableDefault = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1" -const { Flag } = await import("../../src/flag/flag") +const { Flag } = await import("@opencode-ai/core/flag/flag") const { Plugin } = await import("../../src/plugin/index") const { Workspace } = await import("../../src/control-plane/workspace") const { Instance } = await import("../../src/project/instance") diff --git a/packages/opencode/test/server/httpapi-bridge.test.ts b/packages/opencode/test/server/httpapi-bridge.test.ts index 3f0de26a1..35f173371 100644 --- a/packages/opencode/test/server/httpapi-bridge.test.ts +++ b/packages/opencode/test/server/httpapi-bridge.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, test } from "bun:test" import type { UpgradeWebSocket } from "hono/ws" -import { Flag } from "../../src/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Instance } from "../../src/project/instance" import { InstanceRoutes } from "../../src/server/routes/instance" import { FilePaths } from "../../src/server/routes/instance/httpapi/file" diff --git a/packages/opencode/test/server/httpapi-instance.test.ts b/packages/opencode/test/server/httpapi-instance.test.ts index f25d29518..6bd30d2ca 100644 --- a/packages/opencode/test/server/httpapi-instance.test.ts +++ b/packages/opencode/test/server/httpapi-instance.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, test } from "bun:test" import type { UpgradeWebSocket } from "hono/ws" import path from "path" -import { Flag } from "../../src/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { Instance } from "../../src/project/instance" import { InstanceRoutes } from "../../src/server/routes/instance" import { InstancePaths } from "../../src/server/routes/instance/httpapi/instance" diff --git a/packages/opencode/test/storage/db.test.ts b/packages/opencode/test/storage/db.test.ts index 6beb95ac5..2bfaae1da 100644 --- a/packages/opencode/test/storage/db.test.ts +++ b/packages/opencode/test/storage/db.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test" import path from "path" import { Global } from "../../src/global" -import { InstallationChannel } from "../../src/installation/version" +import { InstallationChannel } from "@opencode-ai/core/installation/version" import { Database } from "../../src/storage" describe("Database.Path", () => { diff --git a/packages/opencode/test/sync/index.test.ts b/packages/opencode/test/sync/index.test.ts index d50f0d7c9..c9f6812ca 100644 --- a/packages/opencode/test/sync/index.test.ts +++ b/packages/opencode/test/sync/index.test.ts @@ -7,7 +7,7 @@ import { SyncEvent } from "../../src/sync" import { Database } from "../../src/storage" import { EventTable } from "../../src/sync/event.sql" import { Identifier } from "../../src/id/id" -import { Flag } from "../../src/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { initProjectors } from "../../src/server/projectors" const original = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES diff --git a/packages/opencode/test/workspace/workspace-restore.test.ts b/packages/opencode/test/workspace/workspace-restore.test.ts index ad6ac2c5f..2f8b236b5 100644 --- a/packages/opencode/test/workspace/workspace-restore.test.ts +++ b/packages/opencode/test/workspace/workspace-restore.test.ts @@ -6,7 +6,7 @@ import { registerAdaptor } from "../../src/control-plane/adaptors" import type { WorkspaceAdaptor } from "../../src/control-plane/types" import { Workspace } from "../../src/control-plane/workspace" import { AppRuntime } from "../../src/effect/app-runtime" -import { Flag } from "../../src/flag/flag" +import { Flag } from "@opencode-ai/core/flag/flag" import { ModelID, ProviderID } from "../../src/provider/schema" import { Instance } from "../../src/project/instance" import { Session as SessionNs } from "../../src/session" From 27353df0cc08eab143e5024c6d8fe25577293884 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 17:31:57 +0000 Subject: [PATCH 163/426] chore: generate --- packages/opencode/src/cli/cmd/tui/thread.ts | 7 +- packages/sdk/js/src/v2/gen/types.gen.ts | 6 +- packages/sdk/openapi.json | 120 +++++++++----------- 3 files changed, 61 insertions(+), 72 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 60c5d5ece..7a1c9e721 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -15,7 +15,12 @@ import type { EventSource } from "./context/sdk" import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32" import { writeHeapSnapshot } from "v8" import { TuiConfig } from "./config/tui" -import { OPENCODE_PROCESS_ROLE, OPENCODE_RUN_ID, ensureRunID, sanitizedProcessEnv } from "@opencode-ai/core/util/opencode-process" +import { + OPENCODE_PROCESS_ROLE, + OPENCODE_RUN_ID, + ensureRunID, + sanitizedProcessEnv, +} from "@opencode-ai/core/util/opencode-process" import { validateSession } from "./validate-session" declare global { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 0ad88bb50..40e661b46 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1206,9 +1206,7 @@ export type PermissionRuleConfig = PermissionActionConfig | PermissionObjectConf export type PermissionConfig = | PermissionActionConfig - | ({ - [key: string]: PermissionRuleConfig - } & { + | { read?: PermissionRuleConfig edit?: PermissionRuleConfig glob?: PermissionRuleConfig @@ -1226,7 +1224,7 @@ export type PermissionConfig = doom_loop?: PermissionActionConfig skill?: PermissionRuleConfig [key: string]: PermissionRuleConfig | PermissionActionConfig | undefined - }) + } export type AgentConfig = { model?: string diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 7be58195b..cb0949130 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -10951,73 +10951,60 @@ "$ref": "#/components/schemas/PermissionActionConfig" }, { - "allOf": [ - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/components/schemas/PermissionRuleConfig" - } + "type": "object", + "properties": { + "read": { + "$ref": "#/components/schemas/PermissionRuleConfig" }, - { - "type": "object", - "properties": { - "read": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "edit": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "glob": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "grep": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "list": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "bash": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "task": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "external_directory": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "todowrite": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "question": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "webfetch": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "websearch": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "codesearch": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "lsp": { - "$ref": "#/components/schemas/PermissionRuleConfig" - }, - "doom_loop": { - "$ref": "#/components/schemas/PermissionActionConfig" - }, - "skill": { - "$ref": "#/components/schemas/PermissionRuleConfig" - } - }, - "additionalProperties": { - "$ref": "#/components/schemas/PermissionRuleConfig" - } + "edit": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "glob": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "grep": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "list": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "bash": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "task": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "external_directory": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "todowrite": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "question": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "webfetch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "websearch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "codesearch": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "lsp": { + "$ref": "#/components/schemas/PermissionRuleConfig" + }, + "doom_loop": { + "$ref": "#/components/schemas/PermissionActionConfig" + }, + "skill": { + "$ref": "#/components/schemas/PermissionRuleConfig" } - ] + }, + "additionalProperties": { + "$ref": "#/components/schemas/PermissionRuleConfig" + } } ] }, @@ -11692,8 +11679,7 @@ "type": "boolean" } }, - "required": ["enabled"], - "additionalProperties": false + "required": ["enabled"] } ] } From fc8dae24229c13947a963d4bd69d1d47c26d89cc Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 17:50:26 +0000 Subject: [PATCH 164/426] 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 6be836b12..037e9c57e 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-Dql+RLvSi8gtq7exwetJ4nSjKjJuyKuAf/X8X4L9E9E=", - "aarch64-linux": "sha256-qykwmIBF9tjZBjFo84e8tFix+i49TFM7Pf5jP4lLgEw=", - "aarch64-darwin": "sha256-TteIy5TuuhYsBIQ4q/weUkRnao0Y3iiKvf+noOYPuvg=", - "x86_64-darwin": "sha256-RbAqhuENpO5pT2oVUtVQ8JtUta/5hSnoqTzc3zPRCJc=" + "x86_64-linux": "sha256-0w22pAViYEcELJpOrIVCjTQ73fnsSaJxb75heAIUdYE=", + "aarch64-linux": "sha256-z1fpZ9HQU9n6W5xhKzuUduwQUJa/nrj9WFZdBLL/e/8=", + "aarch64-darwin": "sha256-y5AraTdY2uDTltjQFlHjMoMo6FICgQNKSunIOnQAXnY=", + "x86_64-darwin": "sha256-KQF6dJNQ587xp5h9ET+tLni9dLNwYnzxg2DX+KWfpoE=" } } From 716cf741906db40f07c1aa462001f650370f0093 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sat, 25 Apr 2026 13:52:19 -0400 Subject: [PATCH 165/426] ci: adjust review flow (#24355) --- .github/workflows/review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index 58e73fac8..20f6a89f6 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -45,13 +45,13 @@ jobs: - name: Check PR guidelines compliance env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OPENCODE_PERMISSION: '{ "bash": { "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }' PR_TITLE: ${{ steps.pr-details.outputs.title }} run: | PR_BODY=$(jq -r .body pr_data.json) - opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${PR_TITLE}' + opencode run -m opencode/openai-gpt-5.5 --variant medium "A new pull request has been created: '${PR_TITLE}' ${{ steps.pr-number.outputs.number }} From 705f792e87ac695b64879cda18f2f18d3ace68e3 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 25 Apr 2026 13:51:37 -0400 Subject: [PATCH 166/426] core: move Global module to @opencode-ai/core for centralized path management Move the Global module from packages/opencode/src/global to packages/core/src/global to provide a unified location for managing XDG directories and application paths. This eliminates duplicate path definitions across packages and ensures consistent access to data, config, cache, state, log, and bin directories throughout the codebase. --- packages/core/src/global.ts | 66 +++++++++++-------- packages/opencode/src/agent/agent.ts | 2 +- packages/opencode/src/auth/index.ts | 2 +- packages/opencode/src/cli/cmd/agent.ts | 2 +- packages/opencode/src/cli/cmd/debug/index.ts | 2 +- packages/opencode/src/cli/cmd/mcp.ts | 2 +- packages/opencode/src/cli/cmd/plug.ts | 2 +- packages/opencode/src/cli/cmd/providers.ts | 2 +- .../cli/cmd/tui/component/prompt/frecency.tsx | 2 +- .../cli/cmd/tui/component/prompt/history.tsx | 2 +- .../cli/cmd/tui/component/prompt/stash.tsx | 2 +- .../src/cli/cmd/tui/config/tui-migrate.ts | 2 +- .../opencode/src/cli/cmd/tui/config/tui.ts | 2 +- .../src/cli/cmd/tui/context/directory.ts | 2 +- .../opencode/src/cli/cmd/tui/context/kv.tsx | 2 +- .../src/cli/cmd/tui/context/local.tsx | 2 +- .../src/cli/cmd/tui/context/theme.tsx | 2 +- .../cmd/tui/feature-plugins/home/footer.tsx | 2 +- .../tui/feature-plugins/sidebar/footer.tsx | 2 +- .../src/cli/cmd/tui/plugin/runtime.ts | 2 +- .../src/cli/cmd/tui/routes/session/index.tsx | 2 +- .../cli/cmd/tui/routes/session/permission.tsx | 2 +- packages/opencode/src/cli/cmd/uninstall.ts | 2 +- packages/opencode/src/cli/heap.ts | 2 +- packages/opencode/src/config/config.ts | 2 +- packages/opencode/src/config/paths.ts | 2 +- packages/opencode/src/file/index.ts | 2 +- packages/opencode/src/file/ripgrep.ts | 2 +- packages/opencode/src/global/index.ts | 58 ---------------- packages/opencode/src/index.ts | 2 +- packages/opencode/src/lsp/server.ts | 2 +- packages/opencode/src/mcp/auth.ts | 2 +- packages/opencode/src/plugin/install.ts | 2 +- packages/opencode/src/plugin/meta.ts | 2 +- packages/opencode/src/provider/models.ts | 2 +- packages/opencode/src/provider/provider.ts | 2 +- .../routes/instance/httpapi/instance.ts | 2 +- .../src/server/routes/instance/index.ts | 2 +- packages/opencode/src/session/instruction.ts | 2 +- packages/opencode/src/session/session.ts | 2 +- packages/opencode/src/skill/discovery.ts | 2 +- packages/opencode/src/skill/index.ts | 2 +- packages/opencode/src/snapshot/index.ts | 2 +- packages/opencode/src/storage/db.ts | 2 +- .../opencode/src/storage/json-migration.ts | 2 +- packages/opencode/src/storage/storage.ts | 2 +- packages/opencode/src/tool/truncation-dir.ts | 2 +- packages/opencode/src/util/which.ts | 2 +- packages/opencode/src/worktree/index.ts | 2 +- .../test/cli/tui/plugin-loader.test.ts | 2 +- packages/opencode/test/config/config.test.ts | 2 +- packages/opencode/test/config/tui.test.ts | 2 +- .../test/provider/amazon-bedrock.test.ts | 2 +- .../opencode/test/provider/gitlab-duo.test.ts | 2 +- .../opencode/test/provider/provider.test.ts | 2 +- .../opencode/test/session/instruction.test.ts | 2 +- .../opencode/test/skill/discovery.test.ts | 2 +- packages/opencode/test/storage/db.test.ts | 2 +- .../test/storage/json-migration.test.ts | 2 +- .../opencode/test/storage/storage.test.ts | 2 +- packages/opencode/test/util/log.test.ts | 2 +- 61 files changed, 99 insertions(+), 143 deletions(-) delete mode 100644 packages/opencode/src/global/index.ts diff --git a/packages/core/src/global.ts b/packages/core/src/global.ts index bf605618f..0c83e3a1f 100644 --- a/packages/core/src/global.ts +++ b/packages/core/src/global.ts @@ -1,7 +1,9 @@ import path from "path" +import fs from "fs/promises" import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir" import os from "os" import { Context, Effect, Layer } from "effect" +import { Flock } from "./util/flock" const app = "opencode" const data = path.join(xdgData!, app) @@ -9,7 +11,7 @@ const cache = path.join(xdgCache!, app) const config = path.join(xdgConfig!, app) const state = path.join(xdgState!, app) -export const Path = { +const paths = { get home() { return process.env.OPENCODE_TEST_HOME ?? os.homedir() }, @@ -21,31 +23,43 @@ export const Path = { state, } -export namespace Global { - export class Service extends Context.Service()("@opencode/Global") {} +export const Path = paths - export interface Interface { - readonly home: string - readonly data: string - readonly cache: string - readonly config: string - readonly state: string - readonly bin: string - readonly log: string - } +Flock.setGlobal({ state }) - export const layer = Layer.effect( - Service, - Effect.gen(function* () { - return Service.of({ - home: Path.home, - data: Path.data, - cache: Path.cache, - config: Path.config, - state: Path.state, - bin: Path.bin, - log: Path.log, - }) - }), - ) +await Promise.all([ + fs.mkdir(Path.data, { recursive: true }), + fs.mkdir(Path.config, { recursive: true }), + fs.mkdir(Path.state, { recursive: true }), + fs.mkdir(Path.log, { recursive: true }), + fs.mkdir(Path.bin, { recursive: true }), +]) + +export class Service extends Context.Service()("@opencode/Global") {} + +export interface Interface { + readonly home: string + readonly data: string + readonly cache: string + readonly config: string + readonly state: string + readonly bin: string + readonly log: string } + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + return Service.of({ + home: Path.home, + data: Path.data, + cache: Path.cache, + config: Path.config, + state: Path.state, + bin: Path.bin, + log: Path.log, + }) + }), +) + +export * as Global from "./global" diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 355718b6b..a37e0c194 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -15,7 +15,7 @@ import PROMPT_SUMMARY from "./prompt/summary.txt" import PROMPT_TITLE from "./prompt/title.txt" import { Permission } from "@/permission" import { mergeDeep, pipe, sortBy, values } from "remeda" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import path from "path" import { Plugin } from "@/plugin" import { Skill } from "../skill" diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index 00bc22329..539c40c1a 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -1,7 +1,7 @@ import path from "path" import { Effect, Layer, Record, Result, Schema, Context } from "effect" import { zod } from "@/util/effect-zod" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { AppFileSystem } from "@opencode-ai/core/filesystem" export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key" diff --git a/packages/opencode/src/cli/cmd/agent.ts b/packages/opencode/src/cli/cmd/agent.ts index fd559935f..acad38668 100644 --- a/packages/opencode/src/cli/cmd/agent.ts +++ b/packages/opencode/src/cli/cmd/agent.ts @@ -2,7 +2,7 @@ import { cmd } from "./cmd" import * as prompts from "@clack/prompts" import { AppRuntime } from "@/effect/app-runtime" import { UI } from "../ui" -import { Global } from "../../global" +import { Global } from "@opencode-ai/core/global" import { Agent } from "../../agent/agent" import { Provider } from "../../provider" import path from "path" diff --git a/packages/opencode/src/cli/cmd/debug/index.ts b/packages/opencode/src/cli/cmd/debug/index.ts index e780c4ccb..194e66b1f 100644 --- a/packages/opencode/src/cli/cmd/debug/index.ts +++ b/packages/opencode/src/cli/cmd/debug/index.ts @@ -1,4 +1,4 @@ -import { Global } from "../../../global" +import { Global } from "@opencode-ai/core/global" import { bootstrap } from "../../bootstrap" import { cmd } from "../cmd" import { ConfigCommand } from "./config" diff --git a/packages/opencode/src/cli/cmd/mcp.ts b/packages/opencode/src/cli/cmd/mcp.ts index 3269b4a3d..ef22340fb 100644 --- a/packages/opencode/src/cli/cmd/mcp.ts +++ b/packages/opencode/src/cli/cmd/mcp.ts @@ -13,7 +13,7 @@ import { Instance } from "../../project/instance" import { Installation } from "../../installation" import { InstallationVersion } from "@opencode-ai/core/installation/version" import path from "path" -import { Global } from "../../global" +import { Global } from "@opencode-ai/core/global" import { modify, applyEdits } from "jsonc-parser" import { Filesystem } from "../../util" import { Bus } from "../../bus" diff --git a/packages/opencode/src/cli/cmd/plug.ts b/packages/opencode/src/cli/cmd/plug.ts index 9dfda16d6..14c846f2c 100644 --- a/packages/opencode/src/cli/cmd/plug.ts +++ b/packages/opencode/src/cli/cmd/plug.ts @@ -2,7 +2,7 @@ import { intro, log, outro, spinner } from "@clack/prompts" import type { Argv } from "yargs" import { ConfigPaths } from "../../config" -import { Global } from "../../global" +import { Global } from "@opencode-ai/core/global" import { installPlugin, patchPluginConfig, readPluginManifest } from "../../plugin/install" import { resolvePluginTarget } from "../../plugin/shared" import { Instance } from "../../project/instance" diff --git a/packages/opencode/src/cli/cmd/providers.ts b/packages/opencode/src/cli/cmd/providers.ts index e2eb0b65a..158405e5f 100644 --- a/packages/opencode/src/cli/cmd/providers.ts +++ b/packages/opencode/src/cli/cmd/providers.ts @@ -8,7 +8,7 @@ import { map, pipe, sortBy, values } from "remeda" import path from "path" import os from "os" import { Config } from "../../config" -import { Global } from "../../global" +import { Global } from "@opencode-ai/core/global" import { Plugin } from "../../plugin" import { Instance } from "../../project/instance" import type { Hooks } from "@opencode-ai/plugin" diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx index 929f3a07d..61d4c9e99 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx @@ -1,5 +1,5 @@ import path from "path" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util" import { onMount } from "solid-js" import { createStore } from "solid-js/store" diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx index 03db74de9..2d979ce99 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx @@ -1,5 +1,5 @@ import path from "path" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util" import { onMount } from "solid-js" import { createStore, produce, unwrap } from "solid-js/store" diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx index 84ba62338..a7dd28965 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx @@ -1,5 +1,5 @@ import path from "path" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util" import { onMount } from "solid-js" import { createStore, produce, unwrap } from "solid-js/store" diff --git a/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts b/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts index d5599c170..b6a832dcb 100644 --- a/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts +++ b/packages/opencode/src/cli/cmd/tui/config/tui-migrate.ts @@ -4,7 +4,7 @@ import { unique } from "remeda" import z from "zod" import { TuiInfo, TuiOptions } from "./tui-schema" import { Flag } from "@opencode-ai/core/flag/flag" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem, Log } from "@/util" import * as ConfigPaths from "@/config/paths" diff --git a/packages/opencode/src/cli/cmd/tui/config/tui.ts b/packages/opencode/src/cli/cmd/tui/config/tui.ts index 64ec5f1c5..b55e5807e 100644 --- a/packages/opencode/src/cli/cmd/tui/config/tui.ts +++ b/packages/opencode/src/cli/cmd/tui/config/tui.ts @@ -9,7 +9,7 @@ import { migrateTuiConfig } from "./tui-migrate" import { TuiInfo } from "./tui-schema" import { Flag } from "@opencode-ai/core/flag/flag" import { isRecord } from "@/util/record" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { CurrentWorkingDirectory } from "./cwd" import { ConfigPlugin } from "@/config/plugin" diff --git a/packages/opencode/src/cli/cmd/tui/context/directory.ts b/packages/opencode/src/cli/cmd/tui/context/directory.ts index 81f217398..0c4e5feb9 100644 --- a/packages/opencode/src/cli/cmd/tui/context/directory.ts +++ b/packages/opencode/src/cli/cmd/tui/context/directory.ts @@ -1,7 +1,7 @@ import { createMemo } from "solid-js" import { useProject } from "./project" import { useSync } from "./sync" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" export function useDirectory() { const project = useProject() diff --git a/packages/opencode/src/cli/cmd/tui/context/kv.tsx b/packages/opencode/src/cli/cmd/tui/context/kv.tsx index df8a8394c..2efa314d9 100644 --- a/packages/opencode/src/cli/cmd/tui/context/kv.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/kv.tsx @@ -1,4 +1,4 @@ -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util" import { Flock } from "@opencode-ai/core/util/flock" import { rename, rm } from "fs/promises" diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index 910483764..af06a2bf2 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -5,7 +5,7 @@ import { useSync } from "@tui/context/sync" import { useTheme } from "@tui/context/theme" import { uniqueBy } from "remeda" import path from "path" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { iife } from "@/util/iife" import { useToast } from "../ui/toast" import { useArgs } from "./args" diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx index 10f2dc49d..ca6c0a6cf 100644 --- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx @@ -39,7 +39,7 @@ import carbonfox from "./theme/carbonfox.json" with { type: "json" } import { useKV } from "./kv" import { useRenderer } from "@opentui/solid" import { createStore, produce } from "solid-js/store" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util" import { useTuiConfig } from "./tui-config" import { isRecord } from "@/util/record" diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/footer.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/footer.tsx index 8047c2645..7f2ef55e9 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/footer.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/footer.tsx @@ -1,6 +1,6 @@ import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui" import { createMemo, Match, Show, Switch } from "solid-js" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" const id = "internal:home-footer" diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx index b468d851b..bb51d4f42 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx @@ -1,6 +1,6 @@ import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui" import { createMemo, Show } from "solid-js" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" const id = "internal:sidebar-footer" diff --git a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts index 95d050d7f..556e97684 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts @@ -29,7 +29,7 @@ import { PluginLoader } from "@/plugin/loader" import { PluginMeta } from "@/plugin/meta" import { installPlugin as installModulePlugin, patchPluginConfig, readPluginManifest } from "@/plugin/install" import { hasTheme, upsertTheme } from "../context/theme" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util" import { Process } from "@/util" import { Flock } from "@opencode-ai/core/util/flock" 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 6ba43deb9..516f406ae 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -76,7 +76,7 @@ import stripAnsi from "strip-ansi" import { usePromptRef } from "../../context/prompt" import { useExit } from "../../context/exit" import { Filesystem } from "@/util" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { PermissionPrompt } from "./permission" import { QuestionPrompt } from "./question" import { DialogExportOptions } from "../../ui/dialog-export-options" diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx index e48f348b9..d124734a3 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/permission.tsx @@ -14,7 +14,7 @@ import path from "path" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import { Keybind } from "@/util" import { Locale } from "@/util" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { useDialog } from "../../ui/dialog" import { getScrollAcceleration } from "../../util/scroll" import { useTuiConfig } from "../../context/tui-config" diff --git a/packages/opencode/src/cli/cmd/uninstall.ts b/packages/opencode/src/cli/cmd/uninstall.ts index c0517d491..dc076913b 100644 --- a/packages/opencode/src/cli/cmd/uninstall.ts +++ b/packages/opencode/src/cli/cmd/uninstall.ts @@ -3,7 +3,7 @@ import { UI } from "../ui" import * as prompts from "@clack/prompts" import { AppRuntime } from "@/effect/app-runtime" import { Installation } from "../../installation" -import { Global } from "../../global" +import { Global } from "@opencode-ai/core/global" import fs from "fs/promises" import path from "path" import os from "os" diff --git a/packages/opencode/src/cli/heap.ts b/packages/opencode/src/cli/heap.ts index 0cb4299c5..45557391a 100644 --- a/packages/opencode/src/cli/heap.ts +++ b/packages/opencode/src/cli/heap.ts @@ -1,7 +1,7 @@ import path from "path" import { writeHeapSnapshot } from "node:v8" import { Flag } from "@opencode-ai/core/flag/flag" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Log } from "@/util" const log = Log.create({ service: "heap" }) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 3958e1436..ddd31a3fc 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -4,7 +4,7 @@ import { pathToFileURL } from "url" import os from "os" import z from "zod" import { mergeDeep, pipe } from "remeda" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import fsNode from "fs/promises" import { NamedError } from "@opencode-ai/core/util/error" import { Flag } from "@opencode-ai/core/flag/flag" diff --git a/packages/opencode/src/config/paths.ts b/packages/opencode/src/config/paths.ts index df98bebb2..92c1f45e1 100644 --- a/packages/opencode/src/config/paths.ts +++ b/packages/opencode/src/config/paths.ts @@ -3,7 +3,7 @@ export * as ConfigPaths from "./paths" import path from "path" import { Filesystem } from "@/util" import { Flag } from "@opencode-ai/core/flag/flag" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { unique } from "remeda" import { JsonError } from "./error" import * as Effect from "effect/Effect" diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 4710fd76d..1308d3f69 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -9,7 +9,7 @@ import { formatPatch, structuredPatch } from "diff" import fuzzysort from "fuzzysort" import ignore from "ignore" import path from "path" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { Instance } from "../project/instance" import { Log } from "../util" import { Protected } from "./protected" diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 5602a4c41..ab725b731 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -7,7 +7,7 @@ import { ChildProcess } from "effect/unstable/process" import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Log } from "@/util" import { sanitizedProcessEnv } from "@opencode-ai/core/util/opencode-process" import { which } from "@/util/which" diff --git a/packages/opencode/src/global/index.ts b/packages/opencode/src/global/index.ts deleted file mode 100644 index 7f48a0f88..000000000 --- a/packages/opencode/src/global/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -import fs from "fs/promises" -import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir" -import path from "path" -import os from "os" -import { Filesystem } from "../util" -import { Flock } from "@opencode-ai/core/util/flock" - -const app = "opencode" - -const data = path.join(xdgData!, app) -const cache = path.join(xdgCache!, app) -const config = path.join(xdgConfig!, app) -const state = path.join(xdgState!, app) - -export const Path = { - // Allow override via OPENCODE_TEST_HOME for test isolation - get home() { - return process.env.OPENCODE_TEST_HOME || os.homedir() - }, - data, - bin: path.join(cache, "bin"), - log: path.join(data, "log"), - cache, - config, - state, -} - -// Initialize Flock with global state path -Flock.setGlobal({ state }) - -await Promise.all([ - fs.mkdir(Path.data, { recursive: true }), - fs.mkdir(Path.config, { recursive: true }), - fs.mkdir(Path.state, { recursive: true }), - fs.mkdir(Path.log, { recursive: true }), - fs.mkdir(Path.bin, { recursive: true }), -]) - -const CACHE_VERSION = "21" - -const version = await Filesystem.readText(path.join(Path.cache, "version")).catch(() => "0") - -if (version !== CACHE_VERSION) { - try { - const contents = await fs.readdir(Path.cache) - await Promise.all( - contents.map((item) => - fs.rm(path.join(Path.cache, item), { - recursive: true, - force: true, - }), - ), - ) - } catch {} - await Filesystem.write(path.join(Path.cache, "version"), CACHE_VERSION) -} - -export * as Global from "." diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 3764e1b1c..3c475f133 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -31,7 +31,7 @@ import { PrCommand } from "./cli/cmd/pr" import { SessionCommand } from "./cli/cmd/session" import { DbCommand } from "./cli/cmd/db" import path from "path" -import { Global } from "./global" +import { Global } from "@opencode-ai/core/global" import { JsonMigration } from "./storage" import { Database } from "./storage" import { errorMessage } from "./util/error" diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 9b585c9fb..14b674a98 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -1,7 +1,7 @@ import type { ChildProcessWithoutNullStreams } from "child_process" import path from "path" import os from "os" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { Log } from "../util" import { text } from "node:stream/consumers" import fs from "fs/promises" diff --git a/packages/opencode/src/mcp/auth.ts b/packages/opencode/src/mcp/auth.ts index 0a57fa141..b07d59870 100644 --- a/packages/opencode/src/mcp/auth.ts +++ b/packages/opencode/src/mcp/auth.ts @@ -1,6 +1,6 @@ import path from "path" import z from "zod" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { Effect, Layer, Context } from "effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" diff --git a/packages/opencode/src/plugin/install.ts b/packages/opencode/src/plugin/install.ts index 87798f56d..a76048312 100644 --- a/packages/opencode/src/plugin/install.ts +++ b/packages/opencode/src/plugin/install.ts @@ -8,7 +8,7 @@ import { } from "jsonc-parser" import * as ConfigPaths from "@/config/paths" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util" import { Flock } from "@opencode-ai/core/util/flock" import { isRecord } from "@/util/record" diff --git a/packages/opencode/src/plugin/meta.ts b/packages/opencode/src/plugin/meta.ts index ab067c592..4bc8f5772 100644 --- a/packages/opencode/src/plugin/meta.ts +++ b/packages/opencode/src/plugin/meta.ts @@ -2,7 +2,7 @@ import path from "path" import { fileURLToPath } from "url" import { Flag } from "@opencode-ai/core/flag/flag" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util" import { Flock } from "@opencode-ai/core/util/flock" diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index c3df06abc..8d7d7b03b 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -1,4 +1,4 @@ -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { Log } from "../util" import path from "path" import { Schema } from "effect" diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 96039af9b..c2439d04f 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -16,7 +16,7 @@ import { Flag } from "@opencode-ai/core/flag/flag" import { zod } from "@/util/effect-zod" import { namedSchemaError } from "@/util/named-schema-error" import { iife } from "@/util/iife" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import path from "path" import { pathToFileURL } from "url" import { Effect, Layer, Context, Schema, Types } from "effect" diff --git a/packages/opencode/src/server/routes/instance/httpapi/instance.ts b/packages/opencode/src/server/routes/instance/httpapi/instance.ts index 97b53c1e9..d349ae9cd 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/instance.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/instance.ts @@ -1,4 +1,4 @@ -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Vcs } from "@/project" import * as InstanceState from "@/effect/instance-state" import { Effect, Layer, Schema } from "effect" diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index df50be406..d36964ad1 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -9,7 +9,7 @@ import { Instance } from "@/project/instance" import { Vcs } from "@/project" import { Agent } from "@/agent/agent" import { Skill } from "@/skill" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { LSP } from "@/lsp" import { Command } from "@/command" import { QuestionRoutes } from "./question" diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index 56fca5359..35de71819 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -7,7 +7,7 @@ import { InstanceState } from "@/effect" import { Flag } from "@opencode-ai/core/flag/flag" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { withTransientReadRetry } from "@/util/effect-http-client" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { Log } from "../util" import type { MessageV2 } from "./message-v2" import type { MessageID } from "./schema" diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index db77c0e21..6c67b8517 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -25,7 +25,7 @@ import { SessionID, MessageID, PartID } from "./schema" import type { Provider } from "@/provider" import { Permission } from "@/permission" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Effect, Layer, Option, Context, Schema, Types } from "effect" import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" diff --git a/packages/opencode/src/skill/discovery.ts b/packages/opencode/src/skill/discovery.ts index e620de983..9ce56d4ce 100644 --- a/packages/opencode/src/skill/discovery.ts +++ b/packages/opencode/src/skill/discovery.ts @@ -3,7 +3,7 @@ import { Effect, Layer, Path, Schema, Context } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { withTransientReadRetry } from "@/util/effect-http-client" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { Log } from "../util" const skillConcurrency = 4 diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index a425e13d5..60527cd0b 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -8,7 +8,7 @@ import type { Agent } from "@/agent/agent" import { Bus } from "@/bus" import { InstanceState } from "@/effect" import { Flag } from "@opencode-ai/core/flag/flag" -import { Global } from "@/global" +import { Global } from "@opencode-ai/core/global" import { Permission } from "@/permission" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Config } from "../config" diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 50804ca2b..3701b8210 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -8,7 +8,7 @@ import { InstanceState } from "@/effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Hash } from "@opencode-ai/core/util/hash" import { Config } from "../config" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { Log } from "../util" import { withStatics } from "@/util/schema" import { zod } from "@/util/effect-zod" diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts index 898810581..e442b2a76 100644 --- a/packages/opencode/src/storage/db.ts +++ b/packages/opencode/src/storage/db.ts @@ -4,7 +4,7 @@ import { type SQLiteTransaction } from "drizzle-orm/sqlite-core" export * from "drizzle-orm" import { LocalContext } from "../util" import { lazy } from "../util/lazy" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { Log } from "../util" import { NamedError } from "@opencode-ai/core/util/error" import z from "zod" diff --git a/packages/opencode/src/storage/json-migration.ts b/packages/opencode/src/storage/json-migration.ts index 20ca3ff53..787b50117 100644 --- a/packages/opencode/src/storage/json-migration.ts +++ b/packages/opencode/src/storage/json-migration.ts @@ -1,6 +1,6 @@ import type { SQLiteBunDatabase } from "drizzle-orm/bun-sqlite" import type { NodeSQLiteDatabase } from "drizzle-orm/node-sqlite" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { Log } from "../util" import { ProjectTable } from "../project/project.sql" import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../session/session.sql" diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts index 8f6332677..71cc37e01 100644 --- a/packages/opencode/src/storage/storage.ts +++ b/packages/opencode/src/storage/storage.ts @@ -1,6 +1,6 @@ import { Log } from "../util" import path from "path" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { NamedError } from "@opencode-ai/core/util/error" import z from "zod" import { AppFileSystem } from "@opencode-ai/core/filesystem" diff --git a/packages/opencode/src/tool/truncation-dir.ts b/packages/opencode/src/tool/truncation-dir.ts index d6d5d013d..9ed82e1f3 100644 --- a/packages/opencode/src/tool/truncation-dir.ts +++ b/packages/opencode/src/tool/truncation-dir.ts @@ -1,4 +1,4 @@ import path from "path" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" export const TRUNCATION_DIR = path.join(Global.Path.data, "tool-output") diff --git a/packages/opencode/src/util/which.ts b/packages/opencode/src/util/which.ts index 2e4073914..b9bea421c 100644 --- a/packages/opencode/src/util/which.ts +++ b/packages/opencode/src/util/which.ts @@ -1,6 +1,6 @@ import whichPkg from "which" import path from "path" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" export function which(cmd: string, env?: NodeJS.ProcessEnv) { const base = env?.PATH ?? env?.Path ?? process.env.PATH ?? process.env.Path ?? "" diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index 7539e8d58..b89ac32a9 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -1,6 +1,6 @@ import z from "zod" import { NamedError } from "@opencode-ai/core/util/error" -import { Global } from "../global" +import { Global } from "@opencode-ai/core/global" import { Instance } from "../project/instance" import { InstanceBootstrap } from "../project/bootstrap" import { Project } from "../project" diff --git a/packages/opencode/test/cli/tui/plugin-loader.test.ts b/packages/opencode/test/cli/tui/plugin-loader.test.ts index f5b04ff43..3dd8b6015 100644 --- a/packages/opencode/test/cli/tui/plugin-loader.test.ts +++ b/packages/opencode/test/cli/tui/plugin-loader.test.ts @@ -4,7 +4,7 @@ import path from "path" import { pathToFileURL } from "url" import { tmpdir } from "../../fixture/fixture" import { createTuiPluginApi } from "../../fixture/tui-plugin" -import { Global } from "../../../src/global" +import { Global } from "@opencode-ai/core/global" import { TuiConfig } from "../../../src/cli/cmd/tui/config/tui" import { Filesystem } from "../../../src/util/" diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 3b75e1501..8512236a3 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -23,7 +23,7 @@ const infra = CrossSpawnSpawner.defaultLayer.pipe( import path from "path" import fs from "fs/promises" import { pathToFileURL } from "url" -import { Global } from "../../src/global" +import { Global } from "@opencode-ai/core/global" import { ProjectID } from "../../src/project/schema" import { Filesystem } from "../../src/util" import { ConfigPlugin } from "@/config/plugin" diff --git a/packages/opencode/test/config/tui.test.ts b/packages/opencode/test/config/tui.test.ts index c7b6d4a50..0dbe49cef 100644 --- a/packages/opencode/test/config/tui.test.ts +++ b/packages/opencode/test/config/tui.test.ts @@ -5,7 +5,7 @@ import { tmpdir } from "../fixture/fixture" import { Instance } from "../../src/project/instance" import { TuiConfig } from "../../src/cli/cmd/tui/config/tui" import { Config } from "../../src/config" -import { Global } from "../../src/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem } from "../../src/util" import { AppRuntime } from "../../src/effect/app-runtime" import { Effect, Layer } from "effect" diff --git a/packages/opencode/test/provider/amazon-bedrock.test.ts b/packages/opencode/test/provider/amazon-bedrock.test.ts index 03f83601d..aff67494b 100644 --- a/packages/opencode/test/provider/amazon-bedrock.test.ts +++ b/packages/opencode/test/provider/amazon-bedrock.test.ts @@ -7,7 +7,7 @@ import { tmpdir } from "../fixture/fixture" import { Instance } from "../../src/project/instance" import { Provider } from "../../src/provider" import { Env } from "../../src/env" -import { Global } from "../../src/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem } from "../../src/util" import { Effect } from "effect" import { AppRuntime } from "../../src/effect/app-runtime" diff --git a/packages/opencode/test/provider/gitlab-duo.test.ts b/packages/opencode/test/provider/gitlab-duo.test.ts index 907a32d61..a74ef360b 100644 --- a/packages/opencode/test/provider/gitlab-duo.test.ts +++ b/packages/opencode/test/provider/gitlab-duo.test.ts @@ -11,7 +11,7 @@ export {} // import { Instance } from "../../src/project/instance" // import { Provider } from "../../src/provider" // import { Env } from "../../src/env" -// import { Global } from "../../src/global" +// import { Global } from "@opencode-ai/core/global" // import { GitLabWorkflowLanguageModel } from "gitlab-ai-provider" // test("GitLab Duo: loads provider with API key from environment", async () => { diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 899302082..612fe3e97 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -3,7 +3,7 @@ import { mkdir, unlink } from "fs/promises" import path from "path" import { tmpdir } from "../fixture/fixture" -import { Global } from "../../src/global" +import { Global } from "@opencode-ai/core/global" import { Instance } from "../../src/project/instance" import { Plugin } from "../../src/plugin/index" import { ModelsDev } from "../../src/provider" diff --git a/packages/opencode/test/session/instruction.test.ts b/packages/opencode/test/session/instruction.test.ts index c46bbd20b..60882d2b3 100644 --- a/packages/opencode/test/session/instruction.test.ts +++ b/packages/opencode/test/session/instruction.test.ts @@ -6,7 +6,7 @@ import { Instruction } from "../../src/session/instruction" import type { MessageV2 } from "../../src/session/message-v2" import { Instance } from "../../src/project/instance" import { MessageID, PartID, SessionID } from "../../src/session/schema" -import { Global } from "../../src/global" +import { Global } from "@opencode-ai/core/global" import { tmpdir } from "../fixture/fixture" const run = (effect: Effect.Effect) => diff --git a/packages/opencode/test/skill/discovery.test.ts b/packages/opencode/test/skill/discovery.test.ts index 3f8210329..230a9e03e 100644 --- a/packages/opencode/test/skill/discovery.test.ts +++ b/packages/opencode/test/skill/discovery.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect, beforeAll, afterAll } from "bun:test" import { Effect } from "effect" import { Discovery } from "../../src/skill/discovery" -import { Global } from "../../src/global" +import { Global } from "@opencode-ai/core/global" import { Filesystem } from "../../src/util" import { rm } from "fs/promises" import path from "path" diff --git a/packages/opencode/test/storage/db.test.ts b/packages/opencode/test/storage/db.test.ts index 2bfaae1da..2cd9f817c 100644 --- a/packages/opencode/test/storage/db.test.ts +++ b/packages/opencode/test/storage/db.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" import path from "path" -import { Global } from "../../src/global" +import { Global } from "@opencode-ai/core/global" import { InstallationChannel } from "@opencode-ai/core/installation/version" import { Database } from "../../src/storage" diff --git a/packages/opencode/test/storage/json-migration.test.ts b/packages/opencode/test/storage/json-migration.test.ts index 019faf061..263573794 100644 --- a/packages/opencode/test/storage/json-migration.test.ts +++ b/packages/opencode/test/storage/json-migration.test.ts @@ -6,7 +6,7 @@ import path from "path" import fs from "fs/promises" import { readFileSync, readdirSync } from "fs" import { JsonMigration } from "../../src/storage" -import { Global } from "../../src/global" +import { Global } from "@opencode-ai/core/global" import { ProjectTable } from "../../src/project/project.sql" import { ProjectID } from "../../src/project/schema" import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../../src/session/session.sql" diff --git a/packages/opencode/test/storage/storage.test.ts b/packages/opencode/test/storage/storage.test.ts index 0587b9dd6..6be653ecb 100644 --- a/packages/opencode/test/storage/storage.test.ts +++ b/packages/opencode/test/storage/storage.test.ts @@ -4,7 +4,7 @@ import { Effect, Exit, Layer } from "effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import { Git } from "../../src/git" -import { Global } from "../../src/global" +import { Global } from "@opencode-ai/core/global" import { Storage } from "../../src/storage" import { tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/util/log.test.ts b/packages/opencode/test/util/log.test.ts index 336b16a17..9a3b61732 100644 --- a/packages/opencode/test/util/log.test.ts +++ b/packages/opencode/test/util/log.test.ts @@ -1,7 +1,7 @@ import { afterEach, expect, test } from "bun:test" import fs from "fs/promises" import path from "path" -import { Global } from "../../src/global" +import { Global } from "@opencode-ai/core/global" import { Log } from "../../src/util" import { tmpdir } from "../fixture/fixture" From eb0219988b3696edd67a1b1e076268c7d50f1b47 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 14:00:30 -0400 Subject: [PATCH 167/426] feat(httpapi): bridge catalog read endpoints (#24353) --- packages/opencode/specs/effect/http-api.md | 7 +- packages/opencode/src/agent/agent.ts | 59 ++++++----- packages/opencode/src/command/index.ts | 33 +++---- packages/opencode/src/format/index.ts | 23 +++-- packages/opencode/src/permission/index.ts | 9 +- .../routes/instance/httpapi/instance.ts | 99 ++++++++++++++++++- .../src/server/routes/instance/index.ts | 13 ++- packages/opencode/src/skill/index.ts | 19 ++-- .../test/server/httpapi-instance.test.ts | 27 +++++ 9 files changed, 208 insertions(+), 81 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 1b9da7a2c..20f740e3d 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -140,7 +140,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `file` | `bridged` partial | list/content/status only | | `mcp` | `bridged` partial | status only | | `workspace` | `bridged` | list, get, enter | -| top-level instance reads | `bridged` partial | path and vcs reads; command, agent, skill, lsp, formatter next | +| top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter | | experimental JSON routes | `next/later` | console, tool, worktree, resource, global session list | | `session` | `later/special` | large stateful surface plus streaming | | `sync` | `later` | process/control side effects | @@ -151,8 +151,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Next PRs 1. Produce a generated route inventory from Hono registrations and update `Current Route Status` with exact paths. -2. Continue porting top-level JSON reads. -3. Start the Effect OpenAPI/SDK generation path for already-bridged routes. +2. Start the Effect OpenAPI/SDK generation path for already-bridged routes. ## Checklist @@ -165,7 +164,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho - [x] Add bridge-level auth and instance tests. - [ ] Complete exact Hono route inventory. - [x] Resolve implemented-but-unmounted route groups. -- [ ] Port remaining JSON routes. +- [x] Port remaining top-level JSON reads. - [ ] Generate SDK/OpenAPI from Effect routes. - [ ] Flip ported JSON routes to default-on with fallback. - [ ] Delete replaced Hono route implementations. diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index a37e0c194..231e17467 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -3,7 +3,6 @@ import z from "zod" import { Provider } from "../provider" import { ModelID, ProviderID } from "../provider/schema" import { generateObject, streamObject, type ModelMessage } from "ai" -import { Instance } from "../project/instance" import { Truncate } from "../tool" import { Auth } from "../auth" import { ProviderTransform } from "../provider" @@ -19,37 +18,37 @@ import { Global } from "@opencode-ai/core/global" import path from "path" import { Plugin } from "@/plugin" import { Skill } from "../skill" -import { Effect, Context, Layer } from "effect" +import { Effect, Context, Layer, Schema } from "effect" import { InstanceState } from "@/effect" import * as Option from "effect/Option" import * as OtelTracer from "@effect/opentelemetry/Tracer" +import { zod } from "@/util/effect-zod" +import { withStatics, type DeepMutable } from "@/util/schema" -export const Info = z - .object({ - name: z.string(), - description: z.string().optional(), - mode: z.enum(["subagent", "primary", "all"]), - native: z.boolean().optional(), - hidden: z.boolean().optional(), - topP: z.number().optional(), - temperature: z.number().optional(), - color: z.string().optional(), - permission: Permission.Ruleset.zod, - model: z - .object({ - modelID: ModelID.zod, - providerID: ProviderID.zod, - }) - .optional(), - variant: z.string().optional(), - prompt: z.string().optional(), - options: z.record(z.string(), z.any()), - steps: z.number().int().positive().optional(), - }) - .meta({ - ref: "Agent", - }) -export type Info = z.infer +export const Info = Schema.Struct({ + name: Schema.String, + description: Schema.optional(Schema.String), + mode: Schema.Literals(["subagent", "primary", "all"]), + native: Schema.optional(Schema.Boolean), + hidden: Schema.optional(Schema.Boolean), + topP: Schema.optional(Schema.Number), + temperature: Schema.optional(Schema.Number), + color: Schema.optional(Schema.String), + permission: Permission.Ruleset, + model: Schema.optional( + Schema.Struct({ + modelID: ModelID, + providerID: ProviderID, + }), + ), + variant: Schema.optional(Schema.String), + prompt: Schema.optional(Schema.String), + options: Schema.Record(Schema.String, Schema.Unknown), + steps: Schema.optional(Schema.Number), +}) + .annotate({ identifier: "Agent" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = DeepMutable> export interface Interface { readonly get: (agent: string) => Effect.Effect @@ -79,7 +78,7 @@ export const layer = Layer.effect( const provider = yield* Provider.Service const state = yield* InstanceState.make( - Effect.fn("Agent.state")(function* (_ctx) { + Effect.fn("Agent.state")(function* (ctx) { const cfg = yield* config.get() const skillDirs = yield* skill.dirs() const whitelistedDirs = [Truncate.GLOB, ...skillDirs.map((dir) => path.join(dir, "*"))] @@ -136,7 +135,7 @@ export const layer = Layer.effect( edit: { "*": "deny", [path.join(".opencode", "plans", "*.md")]: "allow", - [path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow", + [path.relative(ctx.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow", }, }), user, diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 478a12f66..7001d4f96 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -5,6 +5,8 @@ import type { InstanceContext } from "@/project/instance" import { SessionID, MessageID } from "@/session/schema" import { Effect, Layer, Context, Schema } from "effect" import z from "zod" +import { zod, ZodOverride } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import { Config } from "../config" import { MCP } from "../mcp" import { Skill } from "../skill" @@ -27,25 +29,22 @@ export const Event = { ), } -export const Info = z - .object({ - name: z.string(), - description: z.string().optional(), - agent: z.string().optional(), - model: z.string().optional(), - source: z.enum(["command", "mcp", "skill"]).optional(), - // workaround for zod not supporting async functions natively so we use getters - // https://zod.dev/v4/changelog?id=zfunction - template: z.promise(z.string()).or(z.string()), - subtask: z.boolean().optional(), - hints: z.array(z.string()), - }) - .meta({ - ref: "Command", - }) +export const Info = Schema.Struct({ + name: Schema.String, + description: Schema.optional(Schema.String), + agent: Schema.optional(Schema.String), + model: Schema.optional(Schema.String), + source: Schema.optional(Schema.Literals(["command", "mcp", "skill"])), + // Some command templates are lazy promises from MCP prompt resolution. + template: Schema.Unknown.annotate({ [ZodOverride]: z.promise(z.string()).or(z.string()) }), + subtask: Schema.optional(Schema.Boolean), + hints: Schema.Array(Schema.String), +}) + .annotate({ identifier: "Command" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) // for some reason zod is inferring `string` for z.promise(z.string()).or(z.string()) so we have to manually override it -export type Info = Omit, "template"> & { template: Promise | string } +export type Info = Omit, "template"> & { template: Promise | string } export function hints(template: string) { const result: string[] = [] diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 53a2c1011..4284a2cf6 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -1,26 +1,25 @@ -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" import { InstanceState } from "@/effect" import path from "path" import { mergeDeep } from "remeda" -import z from "zod" import { Config } from "../config" import { Log } from "../util" import * as Formatter from "./formatter" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" const log = Log.create({ service: "format" }) -export const Status = z - .object({ - name: z.string(), - extensions: z.string().array(), - enabled: z.boolean(), - }) - .meta({ - ref: "FormatterStatus", - }) -export type Status = z.infer +export const Status = Schema.Struct({ + name: Schema.String, + extensions: Schema.Array(Schema.String), + enabled: Schema.Boolean, +}) + .annotate({ identifier: "FormatterStatus" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Status = Schema.Schema.Type export interface Interface { readonly init: () => Effect.Effect diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 428514ecd..2dfa8e940 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -22,13 +22,14 @@ export const Action = Schema.Literals(["allow", "deny", "ask"]) .pipe(withStatics((s) => ({ zod: zod(s) }))) export type Action = Schema.Schema.Type -export class Rule extends Schema.Class("PermissionRule")({ +export const Rule = Schema.Struct({ permission: Schema.String, pattern: Schema.String, action: Action, -}) { - static readonly zod = zod(this) -} +}) + .annotate({ identifier: "PermissionRule" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Rule = Schema.Schema.Type export const Ruleset = Schema.mutable(Schema.Array(Rule)) .annotate({ identifier: "PermissionRuleset" }) diff --git a/packages/opencode/src/server/routes/instance/httpapi/instance.ts b/packages/opencode/src/server/routes/instance/httpapi/instance.ts index d349ae9cd..016703e77 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/instance.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/instance.ts @@ -1,5 +1,10 @@ +import { Agent } from "@/agent/agent" +import { Command } from "@/command" +import { Format } from "@/format" import { Global } from "@opencode-ai/core/global" +import { LSP } from "@/lsp" import { Vcs } from "@/project" +import { Skill } from "@/skill" import * as InstanceState from "@/effect/instance-state" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" @@ -21,6 +26,11 @@ export const InstancePaths = { path: "/path", vcs: "/vcs", vcsDiff: "/vcs/diff", + command: "/command", + agent: "/agent", + skill: "/skill", + lsp: "/lsp", + formatter: "/formatter", } as const export const InstanceApi = HttpApi.make("instance") @@ -57,6 +67,51 @@ export const InstanceApi = HttpApi.make("instance") description: "Retrieve the current git diff for the working tree or against the default branch.", }), ), + HttpApiEndpoint.get("command", InstancePaths.command, { + success: Schema.Array(Command.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "command.list", + summary: "List commands", + description: "Get a list of all available commands in the OpenCode system.", + }), + ), + HttpApiEndpoint.get("agent", InstancePaths.agent, { + success: Schema.Array(Agent.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "app.agents", + summary: "List agents", + description: "Get a list of all available AI agents in the OpenCode system.", + }), + ), + HttpApiEndpoint.get("skill", InstancePaths.skill, { + success: Schema.Array(Skill.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "app.skills", + summary: "List skills", + description: "Get a list of all available skills in the OpenCode system.", + }), + ), + HttpApiEndpoint.get("lsp", InstancePaths.lsp, { + success: Schema.Array(LSP.Status), + }).annotateMerge( + OpenApi.annotations({ + identifier: "lsp.status", + summary: "Get LSP status", + description: "Get LSP server status", + }), + ), + HttpApiEndpoint.get("formatter", InstancePaths.formatter, { + success: Schema.Array(Format.Status), + }).annotateMerge( + OpenApi.annotations({ + identifier: "formatter.status", + summary: "Get formatter status", + description: "Get formatter status", + }), + ), ) .annotateMerge( OpenApi.annotations({ @@ -76,6 +131,11 @@ export const InstanceApi = HttpApi.make("instance") export const instanceHandlers = Layer.unwrap( Effect.gen(function* () { + const agent = yield* Agent.Service + const command = yield* Command.Service + const format = yield* Format.Service + const lsp = yield* LSP.Service + const skill = yield* Skill.Service const vcs = yield* Vcs.Service const getPath = Effect.fn("InstanceHttpApi.path")(function* () { @@ -98,8 +158,43 @@ export const instanceHandlers = Layer.unwrap( return yield* vcs.diff(ctx.query.mode) }) + const getCommand = Effect.fn("InstanceHttpApi.command")(function* () { + return yield* command.list() + }) + + const getAgent = Effect.fn("InstanceHttpApi.agent")(function* () { + return yield* agent.list() + }) + + const getSkill = Effect.fn("InstanceHttpApi.skill")(function* () { + return yield* skill.all() + }) + + const getLsp = Effect.fn("InstanceHttpApi.lsp")(function* () { + return yield* lsp.status() + }) + + const getFormatter = Effect.fn("InstanceHttpApi.formatter")(function* () { + return yield* format.status() + }) + return HttpApiBuilder.group(InstanceApi, "instance", (handlers) => - handlers.handle("path", getPath).handle("vcs", getVcs).handle("vcsDiff", getVcsDiff), + handlers + .handle("path", getPath) + .handle("vcs", getVcs) + .handle("vcsDiff", getVcsDiff) + .handle("command", getCommand) + .handle("agent", getAgent) + .handle("skill", getSkill) + .handle("lsp", getLsp) + .handle("formatter", getFormatter), ) }), -).pipe(Layer.provide(Vcs.defaultLayer)) +).pipe( + Layer.provide(Agent.defaultLayer), + Layer.provide(Command.defaultLayer), + Layer.provide(Format.defaultLayer), + Layer.provide(LSP.defaultLayer), + Layer.provide(Skill.defaultLayer), + Layer.provide(Vcs.defaultLayer), +) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index d36964ad1..fec0bb1ed 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -57,6 +57,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get(InstancePaths.path, (c) => handler(c.req.raw, context)) app.get(InstancePaths.vcs, (c) => handler(c.req.raw, context)) app.get(InstancePaths.vcsDiff, (c) => handler(c.req.raw, context)) + app.get(InstancePaths.command, (c) => handler(c.req.raw, context)) + app.get(InstancePaths.agent, (c) => handler(c.req.raw, context)) + app.get(InstancePaths.skill, (c) => handler(c.req.raw, context)) + app.get(InstancePaths.lsp, (c) => handler(c.req.raw, context)) + app.get(InstancePaths.formatter, (c) => handler(c.req.raw, context)) app.get(McpPaths.status, (c) => handler(c.req.raw, context)) } @@ -201,7 +206,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { description: "List of commands", content: { "application/json": { - schema: resolver(Command.Info.array()), + schema: resolver(Command.Info.zod.array()), }, }, }, @@ -224,7 +229,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { description: "List of agents", content: { "application/json": { - schema: resolver(Agent.Info.array()), + schema: resolver(Agent.Info.zod.array()), }, }, }, @@ -247,7 +252,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { description: "List of skills", content: { "application/json": { - schema: resolver(Skill.Info.array()), + schema: resolver(Skill.Info.zod.array()), }, }, }, @@ -293,7 +298,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { description: "Formatter status", content: { "application/json": { - schema: resolver(Format.Status.array()), + schema: resolver(Format.Status.zod.array()), }, }, }, diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index 60527cd0b..acbb8d3fa 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -2,7 +2,9 @@ import os from "os" import path from "path" import { pathToFileURL } from "url" import z from "zod" -import { Effect, Layer, Context } from "effect" +import { Effect, Layer, Context, Schema } from "effect" +import { zod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import { NamedError } from "@opencode-ai/core/util/error" import type { Agent } from "@/agent/agent" import { Bus } from "@/bus" @@ -23,13 +25,14 @@ const EXTERNAL_SKILL_PATTERN = "skills/**/SKILL.md" const OPENCODE_SKILL_PATTERN = "{skill,skills}/**/SKILL.md" const SKILL_PATTERN = "**/SKILL.md" -export const Info = z.object({ - name: z.string(), - description: z.string(), - location: z.string(), - content: z.string(), +export const Info = Schema.Struct({ + name: Schema.String, + description: Schema.String, + location: Schema.String, + content: Schema.String, }) -export type Info = z.infer + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type Info = Schema.Schema.Type export const InvalidError = NamedError.create( "SkillInvalidError", @@ -91,7 +94,7 @@ const add = Effect.fnUntraced(function* (state: State, match: string, bus: Bus.I if (!md) return - const parsed = Info.pick({ name: true, description: true }).safeParse(md.data) + const parsed = z.object({ name: z.string(), description: z.string() }).safeParse(md.data) if (!parsed.success) return if (state.skills[parsed.data.name]) { diff --git a/packages/opencode/test/server/httpapi-instance.test.ts b/packages/opencode/test/server/httpapi-instance.test.ts index 6bd30d2ca..a066e0e92 100644 --- a/packages/opencode/test/server/httpapi-instance.test.ts +++ b/packages/opencode/test/server/httpapi-instance.test.ts @@ -50,4 +50,31 @@ describe("instance HttpApi", () => { expect.objectContaining({ file: "changed.txt", additions: 1, status: "added" }), ) }) + + test("serves catalog read endpoints through Hono bridge", async () => { + await using tmp = await tmpdir({ config: { formatter: false, lsp: false } }) + + const [commands, agents, skills, lsp, formatter] = await Promise.all([ + app().request(InstancePaths.command, { headers: { "x-opencode-directory": tmp.path } }), + app().request(InstancePaths.agent, { headers: { "x-opencode-directory": tmp.path } }), + app().request(InstancePaths.skill, { headers: { "x-opencode-directory": tmp.path } }), + app().request(InstancePaths.lsp, { headers: { "x-opencode-directory": tmp.path } }), + app().request(InstancePaths.formatter, { headers: { "x-opencode-directory": tmp.path } }), + ]) + + expect(commands.status).toBe(200) + expect(await commands.json()).toContainEqual(expect.objectContaining({ name: "init", source: "command" })) + + expect(agents.status).toBe(200) + expect(await agents.json()).toContainEqual(expect.objectContaining({ name: "build", mode: "primary" })) + + expect(skills.status).toBe(200) + expect(await skills.json()).toBeArray() + + expect(lsp.status).toBe(200) + expect(await lsp.json()).toEqual([]) + + expect(formatter.status).toBe(200) + expect(await formatter.json()).toEqual([]) + }) }) From 3bc0c36acec47aa7ff1278bb222e1224e56641e8 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 18:01:35 +0000 Subject: [PATCH 168/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 34 +++++++++++----------- packages/opencode/src/skill/index.ts | 3 +- packages/sdk/openapi.json | 4 +-- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 20f740e3d..33568e65e 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -130,23 +130,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------ | ----------------- | -------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` partial | reads only; mutation remains Hono | -| `project` | `bridged` partial | reads only; git-init remains Hono | -| `file` | `bridged` partial | list/content/status only | -| `mcp` | `bridged` partial | status only | -| `workspace` | `bridged` | list, get, enter | -| top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter | -| experimental JSON routes | `next/later` | console, tool, worktree, resource, global session list | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `later` | process/control side effects | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| Area | Status | Notes | +| ------------------------ | ----------------- | ------------------------------------------------------ | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` partial | reads only; mutation remains Hono | +| `project` | `bridged` partial | reads only; git-init remains Hono | +| `file` | `bridged` partial | list/content/status only | +| `mcp` | `bridged` partial | status only | +| `workspace` | `bridged` | list, get, enter | +| top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter | +| experimental JSON routes | `next/later` | console, tool, worktree, resource, global session list | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `later` | process/control side effects | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Next PRs diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index acbb8d3fa..ebb433971 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -30,8 +30,7 @@ export const Info = Schema.Struct({ description: Schema.String, location: Schema.String, content: Schema.String, -}) - .pipe(withStatics((s) => ({ zod: zod(s) }))) +}).pipe(withStatics((s) => ({ zod: zod(s) }))) export type Info = Schema.Schema.Type export const InvalidError = NamedError.create( diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index cb0949130..9fb2a3e6d 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -13344,9 +13344,7 @@ "additionalProperties": {} }, "steps": { - "type": "integer", - "exclusiveMinimum": 0, - "maximum": 9007199254740991 + "type": "number" } }, "required": ["name", "mode", "permission", "options"] From 625aca49de2325c29189c6ac6ec5a49efc7e9450 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 14:10:58 -0400 Subject: [PATCH 169/426] feat(tui): read Zed editor context from state db (#24352) --- .../src/cli/cmd/tui/context/editor-zed.ts | 172 ++++++++++++++++++ .../src/cli/cmd/tui/context/editor.ts | 46 ++++- .../test/cli/tui/editor-context.test.ts | 9 + 3 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/context/editor-zed.ts create mode 100644 packages/opencode/test/cli/tui/editor-context.test.ts diff --git a/packages/opencode/src/cli/cmd/tui/context/editor-zed.ts b/packages/opencode/src/cli/cmd/tui/context/editor-zed.ts new file mode 100644 index 000000000..cbf995f8d --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/context/editor-zed.ts @@ -0,0 +1,172 @@ +import { Database } from "bun:sqlite" +import os from "node:os" +import path from "node:path" +import z from "zod" +import { Filesystem } from "@/util" +import type { EditorSelection } from "./editor" + +const ZedEditorRowSchema = z.object({ + editor_id: z.number(), + workspace_id: z.number(), + workspace_paths: z.string().nullable(), + timestamp: z.string(), + buffer_path: z.string().nullable(), + selection_start: z.number().nullable(), + selection_end: z.number().nullable(), +}) + +const ZedEditorContentsSchema = z.object({ + contents: z.string().nullable(), +}) + +type ZedEditorRow = z.infer + +export async function resolveZedSelection(dbPath: string): Promise { + const row = queryZedActiveEditor(dbPath, process.cwd()) + if (!row?.buffer_path || row.selection_start == null || row.selection_end == null) return + + const text = + queryZedEditorContents(dbPath, row) ?? + (await Bun.file(row.buffer_path) + .text() + .catch(() => undefined)) + if (text == null) return + + const startOffset = Math.min(row.selection_start, row.selection_end) + const endOffset = Math.max(row.selection_start, row.selection_end) + + return { + text: text.slice(startOffset, endOffset), + filePath: row.buffer_path, + selection: offsetsToSelection(text, startOffset, endOffset), + } +} + +function queryZedActiveEditor(dbPath: string, cwd: string) { + let db: Database | undefined + try { + db = new Database(dbPath, { readonly: true }) + return db + .query( + `select + e.item_id as editor_id, + e.workspace_id as workspace_id, + w.paths as workspace_paths, + w.timestamp as timestamp, + e.buffer_path as buffer_path, + s.start as selection_start, + s.end as selection_end + from items i + join panes p on p.pane_id = i.pane_id and p.workspace_id = i.workspace_id + join workspaces w on w.workspace_id = i.workspace_id + join editors e on e.item_id = i.item_id and e.workspace_id = i.workspace_id + left join editor_selections s on s.editor_id = e.item_id and s.workspace_id = e.workspace_id + where i.active = 1 and p.active = 1 and i.kind = 'Editor' and e.buffer_path is not null + order by w.timestamp desc`, + ) + .all() + .flatMap((row) => { + const parsed = ZedEditorRowSchema.safeParse(row) + return parsed.success ? [parsed.data] : [] + }) + .map((row) => ({ row, score: scoreZedWorkspace(row.workspace_paths, cwd) })) + .filter((entry) => entry.score > 0) + .sort((left, right) => right.score - left.score || right.row.timestamp.localeCompare(left.row.timestamp))[0]?.row + } catch { + return + } finally { + db?.close() + } +} + +function queryZedEditorContents(dbPath: string, row: ZedEditorRow) { + let db: Database | undefined + try { + db = new Database(dbPath, { readonly: true }) + return ZedEditorContentsSchema.safeParse( + db + .query( + `select contents + from editors + where item_id = $editorID and workspace_id = $workspaceID`, + ) + .get({ $editorID: row.editor_id, $workspaceID: row.workspace_id }), + ).data?.contents + } catch { + return + } finally { + db?.close() + } +} + +export function resolveZedDbPath() { + const candidates = [ + process.env.OPENCODE_ZED_DB, + path.join(os.homedir(), "Library", "Application Support", "Zed", "db", "0-stable", "db.sqlite"), + path.join(os.homedir(), ".local", "share", "zed", "db", "0-stable", "db.sqlite"), + ].filter((item): item is string => Boolean(item)) + + return candidates.find((item) => Filesystem.stat(item)?.isFile()) +} + +function scoreZedWorkspace(workspacePaths: string | null, cwd: string) { + return zedWorkspacePaths(workspacePaths).reduce((score, item) => { + if (pathContains(item, cwd)) return Math.max(score, 2) + if (pathContains(cwd, item)) return Math.max(score, 1) + return score + }, 0) +} + +function zedWorkspacePaths(value: string | null) { + if (!value) return [] + const parsed = parseJson(value) + if (Array.isArray(parsed)) return parsed.filter((item): item is string => typeof item === "string") + return value.split(/\r?\n/).filter(Boolean) +} + +export function offsetToPosition(text: string, offset: number) { + return offsetsToSelection(text, offset, offset).start +} + +function offsetsToSelection(text: string, startOffset: number, endOffset: number) { + const start = Math.max(0, Math.min(startOffset, text.length)) + const end = Math.max(0, Math.min(endOffset, text.length)) + let line = 1 + let lineStart = 0 + let startPosition = position(line, lineStart, start) + let endPosition = position(line, lineStart, end) + + for (let index = 0; index <= end; index++) { + if (index === start) startPosition = position(line, lineStart, index) + if (index === end) { + endPosition = position(line, lineStart, index) + break + } + if (text[index] === "\n") { + line += 1 + lineStart = index + 1 + } + } + + return { start: startPosition, end: endPosition } +} + +function position(line: number, lineStart: number, offset: number) { + return { + line, + character: offset - lineStart + 1, + } +} + +function pathContains(parent: string, child: string) { + const relative = path.relative(path.resolve(parent), path.resolve(child)) + return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)) +} + +function parseJson(value: string) { + try { + return JSON.parse(value) as unknown + } catch { + return + } +} diff --git a/packages/opencode/src/cli/cmd/tui/context/editor.ts b/packages/opencode/src/cli/cmd/tui/context/editor.ts index 4e6c97f6e..75c5440f5 100644 --- a/packages/opencode/src/cli/cmd/tui/context/editor.ts +++ b/packages/opencode/src/cli/cmd/tui/context/editor.ts @@ -4,7 +4,9 @@ import path from "node:path" import { onCleanup, onMount } from "solid-js" import { createStore } from "solid-js/store" import z from "zod" +import { isRecord } from "@/util/record" import { createSimpleContext } from "./helper" +import { resolveZedDbPath, resolveZedSelection } from "./editor-zed" const MCP_PROTOCOL_VERSION = "2025-11-25" @@ -90,6 +92,8 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create let reconnect: ReturnType | undefined let attempt = 0 let requestID = 0 + let zedSelection: Promise | undefined + let lastZedSelectionKey: string | undefined const pending = new Map() const send = (payload: JsonRpcMessage) => { @@ -114,7 +118,29 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create const connection = resolveEditorConnection() if (!connection) { - setStore("status", "disabled") + const dbPath = resolveZedDbPath() + if (!dbPath) { + setStore("status", "disabled") + scheduleReconnect(1000) + return + } + zedSelection ??= resolveZedSelection(dbPath) + .then((selection) => { + if (closed || socket) return + const key = editorSelectionKey(selection) + if (key !== lastZedSelectionKey) { + lastZedSelectionKey = key + setStore("selection", selection) + setStore("status", selection ? "connected" : "disabled") + } + }) + .catch(() => { + if (closed || socket) return + setStore("status", "disabled") + }) + .finally(() => { + zedSelection = undefined + }) scheduleReconnect(1000) return } @@ -196,7 +222,7 @@ export const { use: useEditorContext, provider: EditorContextProvider } = create return { enabled() { - return Boolean(resolveEditorConnection()) + return Boolean(resolveEditorConnection() || resolveZedDbPath()) }, connected() { return store.status === "connected" @@ -289,6 +315,18 @@ function scoreEditorLock(lock: EditorLockFile, cwd: string) { return workspaceMatch * 1_000_000_000_000 + lock.mtimeMs } +function editorSelectionKey(selection: EditorSelection | undefined) { + if (!selection) return "" + return [ + selection.filePath, + selection.selection.start.line, + selection.selection.start.character, + selection.selection.end.line, + selection.selection.end.character, + selection.text, + ].join("\0") +} + function pathContains(parent: string, child: string) { const relative = path.relative(path.resolve(parent), path.resolve(child)) return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)) @@ -313,7 +351,3 @@ function parseMessage(value: unknown) { return } } - -function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value) -} diff --git a/packages/opencode/test/cli/tui/editor-context.test.ts b/packages/opencode/test/cli/tui/editor-context.test.ts new file mode 100644 index 000000000..c605029ca --- /dev/null +++ b/packages/opencode/test/cli/tui/editor-context.test.ts @@ -0,0 +1,9 @@ +import { expect, test } from "bun:test" +import { offsetToPosition } from "../../../src/cli/cmd/tui/context/editor-zed" + +test("offsetToPosition converts Zed offsets to 1-based editor positions", () => { + expect(offsetToPosition("one\ntwo\nthree", 0)).toEqual({ line: 1, character: 1 }) + expect(offsetToPosition("one\ntwo\nthree", 4)).toEqual({ line: 2, character: 1 }) + expect(offsetToPosition("one\ntwo\nthree", 6)).toEqual({ line: 2, character: 3 }) + expect(offsetToPosition("one\ntwo\nthree", 100)).toEqual({ line: 3, character: 6 }) +}) From 05661c60ffb1ce1bde844f3a6aa5b6cb5bc22412 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 14:12:54 -0400 Subject: [PATCH 170/426] feat(httpapi): bridge file search endpoints (#24356) --- packages/opencode/specs/effect/http-api.md | 2 +- .../server/routes/instance/httpapi/file.ts | 89 ++++++++++++++++++- .../src/server/routes/instance/index.ts | 3 + .../opencode/test/server/httpapi-file.test.ts | 20 +++++ 4 files changed, 111 insertions(+), 3 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 33568e65e..948389223 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -137,7 +137,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `provider` | `bridged` | list, auth, OAuth authorize/callback | | `config` | `bridged` partial | reads only; mutation remains Hono | | `project` | `bridged` partial | reads only; git-init remains Hono | -| `file` | `bridged` partial | list/content/status only | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | | `mcp` | `bridged` partial | status only | | `workspace` | `bridged` | list, get, enter | | top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter | diff --git a/packages/opencode/src/server/routes/instance/httpapi/file.ts b/packages/opencode/src/server/routes/instance/httpapi/file.ts index c55d0c2e7..e283bff19 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/file.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/file.ts @@ -1,4 +1,7 @@ import { File } from "@/file" +import { Ripgrep } from "@/file/ripgrep" +import * as InstanceState from "@/effect/instance-state" +import { LSP } from "@/lsp" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" @@ -7,7 +10,31 @@ const FileQuery = Schema.Struct({ path: Schema.String, }) +const FindTextQuery = Schema.Struct({ + pattern: Schema.String, +}) + +const FindFileQuery = Schema.Struct({ + query: Schema.String, + dirs: Schema.optional(Schema.Literals(["true", "false"])), + type: Schema.optional(Schema.Literals(["file", "directory"])), + limit: Schema.optional( + Schema.NumberFromString.check( + Schema.isInt(), + Schema.isGreaterThanOrEqualTo(1), + Schema.isLessThanOrEqualTo(200), + ), + ), +}) + +const FindSymbolQuery = Schema.Struct({ + query: Schema.String, +}) + export const FilePaths = { + findText: "/find", + findFile: "/find/file", + findSymbol: "/find/symbol", list: "/file", content: "/file/content", status: "/file/status", @@ -17,6 +44,36 @@ export const FileApi = HttpApi.make("file") .add( HttpApiGroup.make("file") .add( + HttpApiEndpoint.get("findText", FilePaths.findText, { + query: FindTextQuery, + success: Schema.Array(Ripgrep.SearchMatch), + }).annotateMerge( + OpenApi.annotations({ + identifier: "find.text", + summary: "Find text", + description: "Search for text patterns across files in the project using ripgrep.", + }), + ), + HttpApiEndpoint.get("findFile", FilePaths.findFile, { + query: FindFileQuery, + success: Schema.Array(Schema.String), + }).annotateMerge( + OpenApi.annotations({ + identifier: "find.files", + summary: "Find files", + description: "Search for files or directories by name or pattern in the project directory.", + }), + ), + HttpApiEndpoint.get("findSymbol", FilePaths.findSymbol, { + query: FindSymbolQuery, + success: Schema.Array(LSP.Symbol), + }).annotateMerge( + OpenApi.annotations({ + identifier: "find.symbols", + summary: "Find symbols", + description: "Search for workspace symbols like functions, classes, and variables using LSP.", + }), + ), HttpApiEndpoint.get("list", FilePaths.list, { query: FileQuery, success: Schema.Array(File.Node), @@ -66,6 +123,28 @@ export const FileApi = HttpApi.make("file") export const fileHandlers = Layer.unwrap( Effect.gen(function* () { const svc = yield* File.Service + const ripgrep = yield* Ripgrep.Service + + const findText = Effect.fn("FileHttpApi.findText")(function* (ctx: { query: { pattern: string } }) { + return (yield* ripgrep + .search({ cwd: (yield* InstanceState.context).directory, pattern: ctx.query.pattern, limit: 10 }) + .pipe(Effect.orDie)).items + }) + + const findFile = Effect.fn("FileHttpApi.findFile")(function* (ctx: { + query: { query: string; dirs?: "true" | "false"; type?: "file" | "directory"; limit?: number } + }) { + return yield* svc.search({ + query: ctx.query.query, + limit: ctx.query.limit ?? 10, + dirs: ctx.query.dirs !== "false", + type: ctx.query.type, + }) + }) + + const findSymbol = Effect.fn("FileHttpApi.findSymbol")(function* () { + return [] + }) const list = Effect.fn("FileHttpApi.list")(function* (ctx: { query: { path: string } }) { return yield* svc.list(ctx.query.path) @@ -80,7 +159,13 @@ export const fileHandlers = Layer.unwrap( }) return HttpApiBuilder.group(FileApi, "file", (handlers) => - handlers.handle("list", list).handle("content", content).handle("status", status), + handlers + .handle("findText", findText) + .handle("findFile", findFile) + .handle("findSymbol", findSymbol) + .handle("list", list) + .handle("content", content) + .handle("status", status), ) }), -).pipe(Layer.provide(File.defaultLayer)) +).pipe(Layer.provide(File.defaultLayer), Layer.provide(Ripgrep.defaultLayer)) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index fec0bb1ed..488e43542 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -51,6 +51,9 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.post("/provider/:providerID/oauth/callback", (c) => handler(c.req.raw, context)) app.get("/project", (c) => handler(c.req.raw, context)) app.get("/project/current", (c) => handler(c.req.raw, context)) + app.get(FilePaths.findText, (c) => handler(c.req.raw, context)) + app.get(FilePaths.findFile, (c) => handler(c.req.raw, context)) + app.get(FilePaths.findSymbol, (c) => handler(c.req.raw, context)) app.get(FilePaths.list, (c) => handler(c.req.raw, context)) app.get(FilePaths.content, (c) => handler(c.req.raw, context)) app.get(FilePaths.status, (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/test/server/httpapi-file.test.ts b/packages/opencode/test/server/httpapi-file.test.ts index 5a9058cb6..302e0a349 100644 --- a/packages/opencode/test/server/httpapi-file.test.ts +++ b/packages/opencode/test/server/httpapi-file.test.ts @@ -54,4 +54,24 @@ describe("file HttpApi", () => { expect(status.status).toBe(200) expect(await status.json()).toContainEqual({ path: "hello.txt", added: 1, removed: 0, status: "added" }) }) + + test("serves search endpoints", async () => { + await using tmp = await tmpdir({ git: true }) + await Bun.write(path.join(tmp.path, "hello.txt"), "needle") + + const [text, files, symbols] = await Promise.all([ + request(FilePaths.findText, tmp.path, { pattern: "needle" }), + request(FilePaths.findFile, tmp.path, { query: "hello", type: "file" }), + request(FilePaths.findSymbol, tmp.path, { query: "hello" }), + ]) + + expect(text.status).toBe(200) + expect(await text.json()).toContainEqual(expect.objectContaining({ line_number: 1 })) + + expect(files.status).toBe(200) + expect(await files.json()).toContain("hello.txt") + + expect(symbols.status).toBe(200) + expect(await symbols.json()).toEqual([]) + }) }) From f91b73b9388106205f5cbefca3d79c44a301df5a Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Sat, 25 Apr 2026 14:13:19 -0400 Subject: [PATCH 171/426] ci: fix model name --- .github/workflows/review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index 20f6a89f6..2bd1f0c4a 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -51,7 +51,7 @@ jobs: PR_TITLE: ${{ steps.pr-details.outputs.title }} run: | PR_BODY=$(jq -r .body pr_data.json) - opencode run -m opencode/openai-gpt-5.5 --variant medium "A new pull request has been created: '${PR_TITLE}' + opencode run -m opencode/gpt-5.5 --variant medium "A new pull request has been created: '${PR_TITLE}' ${{ steps.pr-number.outputs.number }} From ff4b60e1f3d2ad83a74785443dd041ed4e7814bf Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 18:14:26 +0000 Subject: [PATCH 172/426] chore: generate --- .../opencode/src/server/routes/instance/httpapi/file.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/file.ts b/packages/opencode/src/server/routes/instance/httpapi/file.ts index e283bff19..5222f4ab8 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/file.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/file.ts @@ -19,11 +19,7 @@ const FindFileQuery = Schema.Struct({ dirs: Schema.optional(Schema.Literals(["true", "false"])), type: Schema.optional(Schema.Literals(["file", "directory"])), limit: Schema.optional( - Schema.NumberFromString.check( - Schema.isInt(), - Schema.isGreaterThanOrEqualTo(1), - Schema.isLessThanOrEqualTo(200), - ), + Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(200)), ), }) From 3eee2f6afa5c7fde20d0e838143832b681795d9f Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 25 Apr 2026 14:22:49 -0400 Subject: [PATCH 173/426] core: move cross-spawn-spawner from opencode to core package Moved the cross-spawn-spawner module from packages/opencode to packages/core to enable code sharing across the monorepo. This consolidates the process spawning infrastructure into the core package so other packages can use cross-platform child process spawning without duplicating the implementation. Updated all import statements across the codebase to reference the new location (@opencode-ai/core/effect/cross-spawn-spawner). Removed the local copy from the opencode package along with its tests. --- bun.lock | 24 +++++++++++-------- packages/core/package.json | 6 ++--- .../src/effect/cross-spawn-spawner.ts | 0 .../test/effect/cross-spawn-spawner.test.ts | 18 ++++++++++---- packages/opencode/src/file/ripgrep.ts | 2 +- packages/opencode/src/format/index.ts | 2 +- packages/opencode/src/git/index.ts | 2 +- packages/opencode/src/installation/index.ts | 2 +- packages/opencode/src/mcp/index.ts | 2 +- packages/opencode/src/npm/index.ts | 2 +- packages/opencode/src/project/project.ts | 2 +- packages/opencode/src/session/prompt.ts | 2 +- packages/opencode/src/snapshot/index.ts | 2 +- packages/opencode/src/tool/registry.ts | 2 +- packages/opencode/src/worktree/index.ts | 2 +- packages/opencode/test/auth/auth.test.ts | 2 +- packages/opencode/test/bus/bus-effect.test.ts | 2 +- packages/opencode/test/config/config.test.ts | 2 +- packages/opencode/test/format/format.test.ts | 2 +- packages/opencode/test/lsp/index.test.ts | 2 +- packages/opencode/test/lsp/lifecycle.test.ts | 2 +- .../opencode/test/permission/next.test.ts | 2 +- .../opencode/test/project/project.test.ts | 2 +- .../test/project/worktree-remove.test.ts | 2 +- .../opencode/test/project/worktree.test.ts | 2 +- .../opencode/test/session/compaction.test.ts | 2 +- .../test/session/processor-effect.test.ts | 2 +- packages/opencode/test/session/prompt.test.ts | 2 +- .../test/session/revert-compact.test.ts | 2 +- .../test/session/snapshot-tool-race.test.ts | 2 +- .../opencode/test/share/share-next.test.ts | 2 +- packages/opencode/test/skill/skill.test.ts | 2 +- .../opencode/test/storage/storage.test.ts | 2 +- packages/opencode/test/tool/bash.test.ts | 2 +- packages/opencode/test/tool/glob.test.ts | 2 +- packages/opencode/test/tool/grep.test.ts | 2 +- packages/opencode/test/tool/lsp.test.ts | 2 +- packages/opencode/test/tool/question.test.ts | 2 +- packages/opencode/test/tool/read.test.ts | 2 +- packages/opencode/test/tool/registry.test.ts | 2 +- packages/opencode/test/tool/skill.test.ts | 2 +- packages/opencode/test/tool/task.test.ts | 2 +- packages/opencode/test/tool/write.test.ts | 2 +- 43 files changed, 69 insertions(+), 57 deletions(-) rename packages/{opencode => core}/src/effect/cross-spawn-spawner.ts (100%) rename packages/{opencode => core}/test/effect/cross-spawn-spawner.test.ts (96%) diff --git a/bun.lock b/bun.lock index 2420ab6df..06a414a21 100644 --- a/bun.lock +++ b/bun.lock @@ -199,24 +199,22 @@ "dependencies": { "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", - "@npmcli/arborist": "catalog:", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", + "cross-spawn": "catalog:", "effect": "catalog:", "glob": "13.0.5", "mime-types": "3.0.2", "minimatch": "10.2.5", - "semver": "catalog:", "xdg-basedir": "5.1.0", "zod": "catalog:", }, "devDependencies": { "@tsconfig/bun": "catalog:", "@types/bun": "catalog:", - "@types/npmcli__arborist": "6.3.3", - "@types/semver": "catalog:", + "@types/cross-spawn": "catalog:", }, }, "packages/desktop": { @@ -2849,7 +2847,7 @@ "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], + "common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], "compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="], @@ -4007,7 +4005,7 @@ "native-duplexpair": ["native-duplexpair@1.0.0", "", {}, "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA=="], - "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], @@ -5517,6 +5515,8 @@ "@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + "@npmcli/arborist/common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], + "@npmcli/query/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], "@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], @@ -5709,8 +5709,6 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], - "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.93", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.69", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@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-hcXDU8QDwpAzLVTuY932TQVlIij9+iaVTxc5mPGY6yb//JMAAC5hMVhg93IrxlrxWLvMgjezNgoZGwquR+SGnw=="], "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="], @@ -5741,8 +5739,6 @@ "astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="], - "astro/common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], - "astro/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="], "astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="], @@ -5897,6 +5893,8 @@ "log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="], @@ -6883,6 +6881,8 @@ "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], + "@electron/rebuild/node-gyp/make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "@electron/rebuild/node-gyp/make-fetch-happen/ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], "@electron/rebuild/node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], @@ -6947,6 +6947,8 @@ "@jsx-email/cli/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], "@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], @@ -7145,6 +7147,8 @@ "js-beautify/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + "opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], "pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], diff --git a/packages/core/package.json b/packages/core/package.json index a244ea8b4..bd826de35 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -18,23 +18,21 @@ "imports": {}, "devDependencies": { "@tsconfig/bun": "catalog:", - "@types/semver": "catalog:", "@types/bun": "catalog:", - "@types/npmcli__arborist": "6.3.3" + "@types/cross-spawn": "catalog:" }, "dependencies": { "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", - "@npmcli/arborist": "catalog:", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "effect": "catalog:", + "cross-spawn": "catalog:", "glob": "13.0.5", "mime-types": "3.0.2", "minimatch": "10.2.5", - "semver": "catalog:", "xdg-basedir": "5.1.0", "zod": "catalog:" }, diff --git a/packages/opencode/src/effect/cross-spawn-spawner.ts b/packages/core/src/effect/cross-spawn-spawner.ts similarity index 100% rename from packages/opencode/src/effect/cross-spawn-spawner.ts rename to packages/core/src/effect/cross-spawn-spawner.ts diff --git a/packages/opencode/test/effect/cross-spawn-spawner.test.ts b/packages/core/test/effect/cross-spawn-spawner.test.ts similarity index 96% rename from packages/opencode/test/effect/cross-spawn-spawner.test.ts rename to packages/core/test/effect/cross-spawn-spawner.test.ts index b4e52529c..d53725797 100644 --- a/packages/opencode/test/effect/cross-spawn-spawner.test.ts +++ b/packages/core/test/effect/cross-spawn-spawner.test.ts @@ -1,11 +1,11 @@ import { describe, expect } from "bun:test" import fs from "node:fs/promises" +import os from "node:os" import path from "node:path" import { Effect, Exit, Stream } from "effect" import type * as PlatformError from "effect/PlatformError" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" -import { tmpdir } from "../fixture/fixture" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { testEffect } from "../lib/effect" const live = CrossSpawnSpawner.defaultLayer @@ -39,11 +39,21 @@ function alive(pid: number) { } } +async function tmpdir() { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-core-test-")) + return { + path: dir, + async [Symbol.asyncDispose]() { + await fs.rm(dir, { recursive: true, force: true }) + }, + } +} + async function gone(pid: number, timeout = 5_000) { const end = Date.now() + timeout while (Date.now() < end) { if (!alive(pid)) return true - await Bun.sleep(50) + await new Promise((resolve) => setTimeout(resolve, 50)) } return !alive(pid) } @@ -395,7 +405,7 @@ describe("cross-spawn spawner", () => { const file = path.join(dir, "echo cmd.cmd") yield* Effect.promise(() => fs.mkdir(dir, { recursive: true })) - yield* Effect.promise(() => Bun.write(file, "@echo off\r\nif %~1==--stdio exit /b 0\r\nexit /b 7\r\n")) + yield* Effect.promise(() => fs.writeFile(file, "@echo off\r\nif %~1==--stdio exit /b 0\r\nexit /b 7\r\n")) const code = yield* ChildProcessSpawner.ChildProcessSpawner.use((svc) => svc.exitCode( diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index ab725b731..dd794ef6f 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -6,7 +6,7 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/ import { ChildProcess } from "effect/unstable/process" import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Global } from "@opencode-ai/core/global" import { Log } from "@/util" import { sanitizedProcessEnv } from "@opencode-ai/core/util/opencode-process" diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 4284a2cf6..2c5943c7d 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -1,6 +1,6 @@ import { Effect, Layer, Context, Schema } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { InstanceState } from "@/effect" import path from "path" import { mergeDeep } from "remeda" diff --git a/packages/opencode/src/git/index.ts b/packages/opencode/src/git/index.ts index 719b5607f..d2e04910a 100644 --- a/packages/opencode/src/git/index.ts +++ b/packages/opencode/src/git/index.ts @@ -1,4 +1,4 @@ -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Effect, Layer, Context, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 1a39d3c61..55c4092e2 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -1,6 +1,6 @@ import { Effect, Layer, Schema, Context, Stream } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { withTransientReadRetry } from "@/util/effect-http-client" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import path from "path" diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 23862db63..9652a1258 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -29,7 +29,7 @@ import { Effect, Exit, Layer, Option, Context, Schema, Stream } from "effect" import { EffectBridge } from "@/effect" import { InstanceState } from "@/effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { zod as effectZod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index ca67491d0..23368b29b 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -14,7 +14,7 @@ import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { makeRuntime } from "@opencode-ai/core/effect/runtime" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import * as CrossSpawnSpawner from "../effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" export class InstallFailedError extends Schema.TaggedErrorClass()("NpmInstallFailedError", { add: Schema.Array(Schema.String).pipe(Schema.optional), diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index e622464b4..c437fedb2 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -12,7 +12,7 @@ import { Effect, Layer, Path, Scope, Context, Stream, Types, Schema } from "effe import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 708961168..87f914a80 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -27,7 +27,7 @@ import { LSP } from "../lsp" import { Flag } from "@opencode-ai/core/flag/flag" import { ulid } from "ulid" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import * as Stream from "effect/Stream" import { Command } from "../command" import { pathToFileURL, fileURLToPath } from "url" diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 3701b8210..32d65633c 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -3,7 +3,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { formatPatch, structuredPatch } from "diff" import path from "path" import z from "zod" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { InstanceState } from "@/effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Hash } from "@opencode-ai/core/util/hash" diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index b7fa696c8..422fd8e3a 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -34,7 +34,7 @@ import { pathToFileURL } from "url" import { Effect, Layer, Context } from "effect" import { FetchHttpClient, HttpClient } from "effect/unstable/http" import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Ripgrep } from "../file/ripgrep" import { Format } from "../format" import { InstanceState } from "@/effect" diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index b89ac32a9..f39d9ad04 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -18,7 +18,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { BootstrapRuntime } from "@/effect/bootstrap-runtime" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { InstanceState } from "@/effect" const log = Log.create({ service: "worktree" }) diff --git a/packages/opencode/test/auth/auth.test.ts b/packages/opencode/test/auth/auth.test.ts index 864649d7a..8688eafaf 100644 --- a/packages/opencode/test/auth/auth.test.ts +++ b/packages/opencode/test/auth/auth.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Auth } from "../../src/auth" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/bus/bus-effect.test.ts b/packages/opencode/test/bus/bus-effect.test.ts index 3d602ae6f..d8b4a275b 100644 --- a/packages/opencode/test/bus/bus-effect.test.ts +++ b/packages/opencode/test/bus/bus-effect.test.ts @@ -3,7 +3,7 @@ import { Deferred, Effect, Layer, Schema, Stream } from "effect" import { Bus } from "../../src/bus" import { BusEvent } from "../../src/bus/bus-event" import { Instance } from "../../src/project/instance" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { provideInstance, provideTmpdirInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 8512236a3..bdd361e7a 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -13,7 +13,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Env } from "../../src/env" import { provideTmpdirInstance } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { testEffect } from "../lib/effect" /** Infra layer that provides FileSystem, Path, ChildProcessSpawner for test fixtures */ diff --git a/packages/opencode/test/format/format.test.ts b/packages/opencode/test/format/format.test.ts index 2f6f235aa..544359c60 100644 --- a/packages/opencode/test/format/format.test.ts +++ b/packages/opencode/test/format/format.test.ts @@ -3,7 +3,7 @@ import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Format } from "../../src/format" import * as Formatter from "../../src/format/formatter" diff --git a/packages/opencode/test/lsp/index.test.ts b/packages/opencode/test/lsp/index.test.ts index d138f56e3..8cb098826 100644 --- a/packages/opencode/test/lsp/index.test.ts +++ b/packages/opencode/test/lsp/index.test.ts @@ -3,7 +3,7 @@ import path from "path" import { Effect, Layer } from "effect" import { LSP } from "../../src/lsp" import { LSPServer } from "../../src/lsp" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/lsp/lifecycle.test.ts b/packages/opencode/test/lsp/lifecycle.test.ts index 13f21c93c..98ac600f4 100644 --- a/packages/opencode/test/lsp/lifecycle.test.ts +++ b/packages/opencode/test/lsp/lifecycle.test.ts @@ -3,7 +3,7 @@ import path from "path" import { Effect, Layer } from "effect" import { LSP } from "../../src/lsp" import { LSPServer } from "../../src/lsp" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index b58c716d8..80601cd9a 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -2,7 +2,7 @@ import { afterEach, test, expect } from "bun:test" import os from "os" import { Cause, Effect, Exit, Fiber, Layer } from "effect" import { Bus } from "../../src/bus" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Permission } from "../../src/permission" import { PermissionID } from "../../src/permission/schema" import { Instance } from "../../src/project/instance" diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index c61df3548..6579b414f 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -10,7 +10,7 @@ import { Effect, Layer, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" void Log.init({ print: false }) diff --git a/packages/opencode/test/project/worktree-remove.test.ts b/packages/opencode/test/project/worktree-remove.test.ts index 5fb2beb28..b0cb626b1 100644 --- a/packages/opencode/test/project/worktree-remove.test.ts +++ b/packages/opencode/test/project/worktree-remove.test.ts @@ -3,7 +3,7 @@ import { describe, expect } from "bun:test" import * as fs from "fs/promises" import path from "path" import { Effect, Layer } from "effect" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Worktree } from "../../src/worktree" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts index c0fe63551..b20914c96 100644 --- a/packages/opencode/test/project/worktree.test.ts +++ b/packages/opencode/test/project/worktree.test.ts @@ -3,7 +3,7 @@ import { afterEach, describe, expect } from "bun:test" import * as fs from "fs/promises" import path from "path" import { Cause, Effect, Exit, Layer } from "effect" -import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Instance } from "../../src/project/instance" import { Worktree } from "../../src/worktree" import { provideInstance, provideTmpdirInstance } from "../fixture/fixture" diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 4fe9c1551..79bdfe41f 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -25,7 +25,7 @@ import * as SessionProcessorModule from "../../src/session/processor" import { Snapshot } from "../../src/snapshot" import { ProviderTest } from "../fake/provider" import { testEffect } from "../lib/effect" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" void Log.init({ print: false }) diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index 74ce91307..d665022fd 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -19,7 +19,7 @@ import { SessionStatus } from "../../src/session/status" import { SessionSummary } from "../../src/session/summary" import { Snapshot } from "../../src/snapshot" import { Log } from "../../src/util" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { provideTmpdirServer } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { raw, reply, TestLLMServer } from "../lib/llm-server" diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 451f1d004..288ca8f99 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -38,7 +38,7 @@ import { Snapshot } from "../../src/snapshot" import { ToolRegistry } from "../../src/tool" import { Truncate } from "../../src/tool" import { Log } from "../../src/util" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { Format } from "../../src/format" import { provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture" diff --git a/packages/opencode/test/session/revert-compact.test.ts b/packages/opencode/test/session/revert-compact.test.ts index f28fb94c0..213e59632 100644 --- a/packages/opencode/test/session/revert-compact.test.ts +++ b/packages/opencode/test/session/revert-compact.test.ts @@ -9,7 +9,7 @@ import { MessageV2 } from "../../src/session/message-v2" import { Snapshot } from "../../src/snapshot" import { Log } from "../../src/util" import { MessageID, PartID, SessionID } from "../../src/session/schema" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index c7e352262..8c8c4da3b 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -52,7 +52,7 @@ import { Snapshot } from "../../src/snapshot" import { ToolRegistry } from "../../src/tool" import { Truncate } from "../../src/tool" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { Format } from "../../src/format" diff --git a/packages/opencode/test/share/share-next.test.ts b/packages/opencode/test/share/share-next.test.ts index e217300d0..41763fe97 100644 --- a/packages/opencode/test/share/share-next.test.ts +++ b/packages/opencode/test/share/share-next.test.ts @@ -6,7 +6,7 @@ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstab import { AccessToken, AccountID, OrgID, RefreshToken } from "../../src/account/schema" import { Account } from "../../src/account/account" import { AccountRepo } from "../../src/account/repo" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Bus } from "../../src/bus" import { Config } from "../../src/config" import { Provider } from "../../src/provider" diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index 21c6c7e65..13f25be5b 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Skill } from "../../src/skill" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { provideInstance, provideTmpdirInstance, tmpdir } from "../fixture/fixture" import { testEffect } from "../lib/effect" import path from "path" diff --git a/packages/opencode/test/storage/storage.test.ts b/packages/opencode/test/storage/storage.test.ts index 6be653ecb..f1b245f9b 100644 --- a/packages/opencode/test/storage/storage.test.ts +++ b/packages/opencode/test/storage/storage.test.ts @@ -2,7 +2,7 @@ import { describe, expect } from "bun:test" import path from "path" import { Effect, Exit, Layer } from "effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Git } from "../../src/git" import { Global } from "@opencode-ai/core/global" import { Storage } from "../../src/storage" diff --git a/packages/opencode/test/tool/bash.test.ts b/packages/opencode/test/tool/bash.test.ts index fd35c9aeb..23f18f989 100644 --- a/packages/opencode/test/tool/bash.test.ts +++ b/packages/opencode/test/tool/bash.test.ts @@ -11,7 +11,7 @@ import type { Permission } from "../../src/permission" import { Agent } from "../../src/agent/agent" import { Truncate } from "../../src/tool" import { SessionID, MessageID } from "../../src/session/schema" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Plugin } from "../../src/plugin" diff --git a/packages/opencode/test/tool/glob.test.ts b/packages/opencode/test/tool/glob.test.ts index c37e7b35f..8d496509a 100644 --- a/packages/opencode/test/tool/glob.test.ts +++ b/packages/opencode/test/tool/glob.test.ts @@ -3,7 +3,7 @@ import path from "path" import { Cause, Effect, Exit, Layer } from "effect" import { GlobTool } from "../../src/tool/glob" import { SessionID, MessageID } from "../../src/session/schema" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Truncate } from "../../src/tool" diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index a279574e1..3e147dddc 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -4,7 +4,7 @@ import { Effect, Layer } from "effect" import { GrepTool } from "../../src/tool/grep" import { provideInstance, provideTmpdirInstance } from "../fixture/fixture" import { SessionID, MessageID } from "../../src/session/schema" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Truncate } from "../../src/tool" import { Agent } from "../../src/agent/agent" import { Ripgrep } from "../../src/file/ripgrep" diff --git a/packages/opencode/test/tool/lsp.test.ts b/packages/opencode/test/tool/lsp.test.ts index b9d48e69a..07de4a0da 100644 --- a/packages/opencode/test/tool/lsp.test.ts +++ b/packages/opencode/test/tool/lsp.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect } from "bun:test" import { Effect, Layer } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { LSP } from "../../src/lsp" import { Permission } from "../../src/permission" diff --git a/packages/opencode/test/tool/question.test.ts b/packages/opencode/test/tool/question.test.ts index 17718b2b3..53c413186 100644 --- a/packages/opencode/test/tool/question.test.ts +++ b/packages/opencode/test/tool/question.test.ts @@ -4,7 +4,7 @@ import { QuestionTool } from "../../src/tool/question" import { Question } from "../../src/question" import { SessionID, MessageID } from "../../src/session/schema" import { Agent } from "../../src/agent/agent" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Truncate } from "../../src/tool" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index 7c3bf51fe..27e6b71c5 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect } from "bun:test" import { Cause, Effect, Exit, Layer } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { LSP } from "../../src/lsp" import { Permission } from "../../src/permission" diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index dbb89e09a..54c1d7706 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -3,7 +3,7 @@ import path from "path" import fs from "fs/promises" import { Effect, Layer } from "effect" import { Instance } from "../../src/project/instance" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { ToolRegistry } from "../../src/tool" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index b12940e4d..c67121e3c 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -1,4 +1,4 @@ -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Effect, Layer } from "effect" import { afterEach, describe, expect } from "bun:test" import path from "path" diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index b94dd5208..1eaa0cfc8 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Agent } from "../../src/agent/agent" import { Config } from "../../src/config" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { Instance } from "../../src/project/instance" import { Session } from "../../src/session" import { MessageV2 } from "../../src/session/message-v2" diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts index 0714d2d02..706ddb372 100644 --- a/packages/opencode/test/tool/write.test.ts +++ b/packages/opencode/test/tool/write.test.ts @@ -12,7 +12,7 @@ import { Truncate } from "../../src/tool" import { Tool } from "../../src/tool" import { Agent } from "../../src/agent/agent" import { SessionID, MessageID } from "../../src/session/schema" -import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" From 1e98167b0e027825b3a77a2dab0be539011e7376 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 25 Apr 2026 14:30:16 -0400 Subject: [PATCH 174/426] core: move cross-spawn-spawner to root and remove unused types The cross-spawn-spawner module has been moved from src/effect/ to src/ to simplify the core package structure. The src/types.d.ts file which contained unused type declarations has also been removed. All imports throughout the codebase have been updated to reflect the new location. This change reduces the package's internal complexity by flattening the module hierarchy and removing dead code, making future maintenance easier. --- .../src/{effect => }/cross-spawn-spawner.ts | 0 packages/core/src/types.d.ts | 46 ------------------- .../test/effect/cross-spawn-spawner.test.ts | 2 +- packages/opencode/src/file/ripgrep.ts | 2 +- packages/opencode/src/format/index.ts | 2 +- packages/opencode/src/git/index.ts | 2 +- packages/opencode/src/installation/index.ts | 2 +- packages/opencode/src/mcp/index.ts | 2 +- packages/opencode/src/npm/index.ts | 2 +- packages/opencode/src/project/project.ts | 2 +- packages/opencode/src/session/prompt.ts | 2 +- packages/opencode/src/snapshot/index.ts | 2 +- packages/opencode/src/tool/registry.ts | 2 +- packages/opencode/src/worktree/index.ts | 2 +- packages/opencode/test/auth/auth.test.ts | 2 +- packages/opencode/test/bus/bus-effect.test.ts | 2 +- packages/opencode/test/config/config.test.ts | 2 +- packages/opencode/test/format/format.test.ts | 2 +- packages/opencode/test/lsp/index.test.ts | 2 +- packages/opencode/test/lsp/lifecycle.test.ts | 2 +- .../opencode/test/permission/next.test.ts | 2 +- .../opencode/test/project/project.test.ts | 2 +- .../test/project/worktree-remove.test.ts | 2 +- .../opencode/test/project/worktree.test.ts | 2 +- .../opencode/test/session/compaction.test.ts | 2 +- .../test/session/processor-effect.test.ts | 2 +- packages/opencode/test/session/prompt.test.ts | 2 +- .../test/session/revert-compact.test.ts | 2 +- .../test/session/snapshot-tool-race.test.ts | 2 +- .../opencode/test/share/share-next.test.ts | 2 +- packages/opencode/test/skill/skill.test.ts | 2 +- .../opencode/test/storage/storage.test.ts | 2 +- packages/opencode/test/tool/bash.test.ts | 2 +- packages/opencode/test/tool/glob.test.ts | 2 +- packages/opencode/test/tool/grep.test.ts | 2 +- packages/opencode/test/tool/lsp.test.ts | 2 +- packages/opencode/test/tool/question.test.ts | 2 +- packages/opencode/test/tool/read.test.ts | 2 +- packages/opencode/test/tool/registry.test.ts | 2 +- packages/opencode/test/tool/skill.test.ts | 2 +- packages/opencode/test/tool/task.test.ts | 2 +- packages/opencode/test/tool/write.test.ts | 2 +- 42 files changed, 40 insertions(+), 86 deletions(-) rename packages/core/src/{effect => }/cross-spawn-spawner.ts (100%) delete mode 100644 packages/core/src/types.d.ts diff --git a/packages/core/src/effect/cross-spawn-spawner.ts b/packages/core/src/cross-spawn-spawner.ts similarity index 100% rename from packages/core/src/effect/cross-spawn-spawner.ts rename to packages/core/src/cross-spawn-spawner.ts diff --git a/packages/core/src/types.d.ts b/packages/core/src/types.d.ts deleted file mode 100644 index 60e1639ad..000000000 --- a/packages/core/src/types.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -declare module "@npmcli/arborist" { - export interface ArboristOptions { - path: string - binLinks?: boolean - progress?: boolean - savePrefix?: string - ignoreScripts?: boolean - [key: string]: unknown - } - - export interface ArboristNode { - name: string - path: string - } - - export interface ArboristEdge { - to?: ArboristNode - } - - export interface ArboristTree { - edgesOut: Map - } - - export interface ReifyOptions { - add?: string[] - save?: boolean - saveType?: "prod" | "dev" | "optional" | "peer" - [key: string]: unknown - } - - export class Arborist { - constructor(options: ArboristOptions) - loadVirtual(): Promise - reify(options?: ReifyOptions): Promise - } -} - -declare var Bun: - | { - file(path: string): { - text(): Promise - json(): Promise - } - write(path: string, content: string | Uint8Array): Promise - } - | undefined diff --git a/packages/core/test/effect/cross-spawn-spawner.test.ts b/packages/core/test/effect/cross-spawn-spawner.test.ts index d53725797..2612b75e4 100644 --- a/packages/core/test/effect/cross-spawn-spawner.test.ts +++ b/packages/core/test/effect/cross-spawn-spawner.test.ts @@ -5,7 +5,7 @@ import path from "node:path" import { Effect, Exit, Stream } from "effect" import type * as PlatformError from "effect/PlatformError" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { testEffect } from "../lib/effect" const live = CrossSpawnSpawner.defaultLayer diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index dd794ef6f..da5981737 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -6,7 +6,7 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/ import { ChildProcess } from "effect/unstable/process" import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Global } from "@opencode-ai/core/global" import { Log } from "@/util" import { sanitizedProcessEnv } from "@opencode-ai/core/util/opencode-process" diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 2c5943c7d..9fa53293f 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -1,6 +1,6 @@ import { Effect, Layer, Context, Schema } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { InstanceState } from "@/effect" import path from "path" import { mergeDeep } from "remeda" diff --git a/packages/opencode/src/git/index.ts b/packages/opencode/src/git/index.ts index d2e04910a..16a862447 100644 --- a/packages/opencode/src/git/index.ts +++ b/packages/opencode/src/git/index.ts @@ -1,4 +1,4 @@ -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Effect, Layer, Context, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 55c4092e2..84fd02cb3 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -1,6 +1,6 @@ import { Effect, Layer, Schema, Context, Stream } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { withTransientReadRetry } from "@/util/effect-http-client" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import path from "path" diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 9652a1258..c2479372d 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -29,7 +29,7 @@ import { Effect, Exit, Layer, Option, Context, Schema, Stream } from "effect" import { EffectBridge } from "@/effect" import { InstanceState } from "@/effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { zod as effectZod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 23368b29b..a7538b9c3 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -14,7 +14,7 @@ import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { makeRuntime } from "@opencode-ai/core/effect/runtime" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" export class InstallFailedError extends Schema.TaggedErrorClass()("NpmInstallFailedError", { add: Schema.Array(Schema.String).pipe(Schema.optional), diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index c437fedb2..fc34a6296 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -12,7 +12,7 @@ import { Effect, Layer, Path, Scope, Context, Stream, Types, Schema } from "effe import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 87f914a80..600eb42f7 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -27,7 +27,7 @@ import { LSP } from "../lsp" import { Flag } from "@opencode-ai/core/flag/flag" import { ulid } from "ulid" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import * as Stream from "effect/Stream" import { Command } from "../command" import { pathToFileURL, fileURLToPath } from "url" diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 32d65633c..88ea274f8 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -3,7 +3,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { formatPatch, structuredPatch } from "diff" import path from "path" import z from "zod" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { InstanceState } from "@/effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Hash } from "@opencode-ai/core/util/hash" diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 422fd8e3a..4e3d3d714 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -34,7 +34,7 @@ import { pathToFileURL } from "url" import { Effect, Layer, Context } from "effect" import { FetchHttpClient, HttpClient } from "effect/unstable/http" import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Ripgrep } from "../file/ripgrep" import { Format } from "../format" import { InstanceState } from "@/effect" diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index f39d9ad04..b9c7226b6 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -18,7 +18,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { BootstrapRuntime } from "@/effect/bootstrap-runtime" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { InstanceState } from "@/effect" const log = Log.create({ service: "worktree" }) diff --git a/packages/opencode/test/auth/auth.test.ts b/packages/opencode/test/auth/auth.test.ts index 8688eafaf..55e950aab 100644 --- a/packages/opencode/test/auth/auth.test.ts +++ b/packages/opencode/test/auth/auth.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Auth } from "../../src/auth" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/bus/bus-effect.test.ts b/packages/opencode/test/bus/bus-effect.test.ts index d8b4a275b..0daf8fe6a 100644 --- a/packages/opencode/test/bus/bus-effect.test.ts +++ b/packages/opencode/test/bus/bus-effect.test.ts @@ -3,7 +3,7 @@ import { Deferred, Effect, Layer, Schema, Stream } from "effect" import { Bus } from "../../src/bus" import { BusEvent } from "../../src/bus/bus-event" import { Instance } from "../../src/project/instance" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideInstance, provideTmpdirInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index bdd361e7a..f6fb04ea0 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -13,7 +13,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Env } from "../../src/env" import { provideTmpdirInstance } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { testEffect } from "../lib/effect" /** Infra layer that provides FileSystem, Path, ChildProcessSpawner for test fixtures */ diff --git a/packages/opencode/test/format/format.test.ts b/packages/opencode/test/format/format.test.ts index 544359c60..674d2767c 100644 --- a/packages/opencode/test/format/format.test.ts +++ b/packages/opencode/test/format/format.test.ts @@ -3,7 +3,7 @@ import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Format } from "../../src/format" import * as Formatter from "../../src/format/formatter" diff --git a/packages/opencode/test/lsp/index.test.ts b/packages/opencode/test/lsp/index.test.ts index 8cb098826..092bfef51 100644 --- a/packages/opencode/test/lsp/index.test.ts +++ b/packages/opencode/test/lsp/index.test.ts @@ -3,7 +3,7 @@ import path from "path" import { Effect, Layer } from "effect" import { LSP } from "../../src/lsp" import { LSPServer } from "../../src/lsp" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/lsp/lifecycle.test.ts b/packages/opencode/test/lsp/lifecycle.test.ts index 98ac600f4..e39290973 100644 --- a/packages/opencode/test/lsp/lifecycle.test.ts +++ b/packages/opencode/test/lsp/lifecycle.test.ts @@ -3,7 +3,7 @@ import path from "path" import { Effect, Layer } from "effect" import { LSP } from "../../src/lsp" import { LSPServer } from "../../src/lsp" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index 80601cd9a..0064185f4 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -2,7 +2,7 @@ import { afterEach, test, expect } from "bun:test" import os from "os" import { Cause, Effect, Exit, Fiber, Layer } from "effect" import { Bus } from "../../src/bus" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Permission } from "../../src/permission" import { PermissionID } from "../../src/permission/schema" import { Instance } from "../../src/project/instance" diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index 6579b414f..c3aba55de 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -10,7 +10,7 @@ import { Effect, Layer, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" void Log.init({ print: false }) diff --git a/packages/opencode/test/project/worktree-remove.test.ts b/packages/opencode/test/project/worktree-remove.test.ts index b0cb626b1..fa70ecb89 100644 --- a/packages/opencode/test/project/worktree-remove.test.ts +++ b/packages/opencode/test/project/worktree-remove.test.ts @@ -3,7 +3,7 @@ import { describe, expect } from "bun:test" import * as fs from "fs/promises" import path from "path" import { Effect, Layer } from "effect" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Worktree } from "../../src/worktree" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts index b20914c96..44a25a8e6 100644 --- a/packages/opencode/test/project/worktree.test.ts +++ b/packages/opencode/test/project/worktree.test.ts @@ -3,7 +3,7 @@ import { afterEach, describe, expect } from "bun:test" import * as fs from "fs/promises" import path from "path" import { Cause, Effect, Exit, Layer } from "effect" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Instance } from "../../src/project/instance" import { Worktree } from "../../src/worktree" import { provideInstance, provideTmpdirInstance } from "../fixture/fixture" diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 79bdfe41f..1b2b120b6 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -25,7 +25,7 @@ import * as SessionProcessorModule from "../../src/session/processor" import { Snapshot } from "../../src/snapshot" import { ProviderTest } from "../fake/provider" import { testEffect } from "../lib/effect" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" void Log.init({ print: false }) diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index d665022fd..fee42a939 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -19,7 +19,7 @@ import { SessionStatus } from "../../src/session/status" import { SessionSummary } from "../../src/session/summary" import { Snapshot } from "../../src/snapshot" import { Log } from "../../src/util" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirServer } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { raw, reply, TestLLMServer } from "../lib/llm-server" diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 288ca8f99..7e3377746 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -38,7 +38,7 @@ import { Snapshot } from "../../src/snapshot" import { ToolRegistry } from "../../src/tool" import { Truncate } from "../../src/tool" import { Log } from "../../src/util" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { Format } from "../../src/format" import { provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture" diff --git a/packages/opencode/test/session/revert-compact.test.ts b/packages/opencode/test/session/revert-compact.test.ts index 213e59632..d77ef3e05 100644 --- a/packages/opencode/test/session/revert-compact.test.ts +++ b/packages/opencode/test/session/revert-compact.test.ts @@ -9,7 +9,7 @@ import { MessageV2 } from "../../src/session/message-v2" import { Snapshot } from "../../src/snapshot" import { Log } from "../../src/util" import { MessageID, PartID, SessionID } from "../../src/session/schema" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 8c8c4da3b..269c23148 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -52,7 +52,7 @@ import { Snapshot } from "../../src/snapshot" import { ToolRegistry } from "../../src/tool" import { Truncate } from "../../src/tool" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { Format } from "../../src/format" diff --git a/packages/opencode/test/share/share-next.test.ts b/packages/opencode/test/share/share-next.test.ts index 41763fe97..5470d654a 100644 --- a/packages/opencode/test/share/share-next.test.ts +++ b/packages/opencode/test/share/share-next.test.ts @@ -6,7 +6,7 @@ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstab import { AccessToken, AccountID, OrgID, RefreshToken } from "../../src/account/schema" import { Account } from "../../src/account/account" import { AccountRepo } from "../../src/account/repo" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Bus } from "../../src/bus" import { Config } from "../../src/config" import { Provider } from "../../src/provider" diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index 13f25be5b..bfcb0dcd6 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Skill } from "../../src/skill" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideInstance, provideTmpdirInstance, tmpdir } from "../fixture/fixture" import { testEffect } from "../lib/effect" import path from "path" diff --git a/packages/opencode/test/storage/storage.test.ts b/packages/opencode/test/storage/storage.test.ts index f1b245f9b..a3d5a8ac5 100644 --- a/packages/opencode/test/storage/storage.test.ts +++ b/packages/opencode/test/storage/storage.test.ts @@ -2,7 +2,7 @@ import { describe, expect } from "bun:test" import path from "path" import { Effect, Exit, Layer } from "effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Git } from "../../src/git" import { Global } from "@opencode-ai/core/global" import { Storage } from "../../src/storage" diff --git a/packages/opencode/test/tool/bash.test.ts b/packages/opencode/test/tool/bash.test.ts index 23f18f989..32cd43100 100644 --- a/packages/opencode/test/tool/bash.test.ts +++ b/packages/opencode/test/tool/bash.test.ts @@ -11,7 +11,7 @@ import type { Permission } from "../../src/permission" import { Agent } from "../../src/agent/agent" import { Truncate } from "../../src/tool" import { SessionID, MessageID } from "../../src/session/schema" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Plugin } from "../../src/plugin" diff --git a/packages/opencode/test/tool/glob.test.ts b/packages/opencode/test/tool/glob.test.ts index 8d496509a..a8637ea9c 100644 --- a/packages/opencode/test/tool/glob.test.ts +++ b/packages/opencode/test/tool/glob.test.ts @@ -3,7 +3,7 @@ import path from "path" import { Cause, Effect, Exit, Layer } from "effect" import { GlobTool } from "../../src/tool/glob" import { SessionID, MessageID } from "../../src/session/schema" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Truncate } from "../../src/tool" diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index 3e147dddc..acdaff03a 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -4,7 +4,7 @@ import { Effect, Layer } from "effect" import { GrepTool } from "../../src/tool/grep" import { provideInstance, provideTmpdirInstance } from "../fixture/fixture" import { SessionID, MessageID } from "../../src/session/schema" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Truncate } from "../../src/tool" import { Agent } from "../../src/agent/agent" import { Ripgrep } from "../../src/file/ripgrep" diff --git a/packages/opencode/test/tool/lsp.test.ts b/packages/opencode/test/tool/lsp.test.ts index 07de4a0da..b7a52da19 100644 --- a/packages/opencode/test/tool/lsp.test.ts +++ b/packages/opencode/test/tool/lsp.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect } from "bun:test" import { Effect, Layer } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { LSP } from "../../src/lsp" import { Permission } from "../../src/permission" diff --git a/packages/opencode/test/tool/question.test.ts b/packages/opencode/test/tool/question.test.ts index 53c413186..537d1f950 100644 --- a/packages/opencode/test/tool/question.test.ts +++ b/packages/opencode/test/tool/question.test.ts @@ -4,7 +4,7 @@ import { QuestionTool } from "../../src/tool/question" import { Question } from "../../src/question" import { SessionID, MessageID } from "../../src/session/schema" import { Agent } from "../../src/agent/agent" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Truncate } from "../../src/tool" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index 27e6b71c5..b9c313bdc 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect } from "bun:test" import { Cause, Effect, Exit, Layer } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { LSP } from "../../src/lsp" import { Permission } from "../../src/permission" diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 54c1d7706..523352d41 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -3,7 +3,7 @@ import path from "path" import fs from "fs/promises" import { Effect, Layer } from "effect" import { Instance } from "../../src/project/instance" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { ToolRegistry } from "../../src/tool" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index c67121e3c..43659187f 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -1,4 +1,4 @@ -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Effect, Layer } from "effect" import { afterEach, describe, expect } from "bun:test" import path from "path" diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index 1eaa0cfc8..490b3f200 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Agent } from "../../src/agent/agent" import { Config } from "../../src/config" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Instance } from "../../src/project/instance" import { Session } from "../../src/session" import { MessageV2 } from "../../src/session/message-v2" diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts index 706ddb372..e6e383189 100644 --- a/packages/opencode/test/tool/write.test.ts +++ b/packages/opencode/test/tool/write.test.ts @@ -12,7 +12,7 @@ import { Truncate } from "../../src/tool" import { Tool } from "../../src/tool" import { Agent } from "../../src/agent/agent" import { SessionID, MessageID } from "../../src/session/schema" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" From f5dce6d960ea44d3a77cfe868b8209b44a866edb Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 25 Apr 2026 14:36:15 -0400 Subject: [PATCH 175/426] core: move npm service to core package for shared dependency management --- bun.lock | 28 ++++++------ packages/core/package.json | 9 +++- .../src/npm/index.ts => core/src/npm.ts} | 16 ++++--- packages/opencode/package.json | 3 -- .../opencode/src/cli/cmd/tui/config/tui.ts | 2 +- packages/opencode/src/cli/cmd/tui/layer.ts | 2 +- packages/opencode/src/config/config.ts | 2 +- packages/opencode/src/effect/app-runtime.ts | 2 +- packages/opencode/src/format/formatter.ts | 2 +- packages/opencode/src/lsp/server.ts | 2 +- packages/opencode/src/npm/config.ts | 0 packages/opencode/src/npmcli-config.d.ts | 43 ------------------- packages/opencode/src/plugin/shared.ts | 2 +- packages/opencode/src/provider/provider.ts | 2 +- .../cli/tui/plugin-loader-entrypoint.test.ts | 2 +- packages/opencode/test/config/config.test.ts | 2 +- packages/opencode/test/npm.test.ts | 2 +- .../test/plugin/loader-shared.test.ts | 2 +- 18 files changed, 42 insertions(+), 81 deletions(-) rename packages/{opencode/src/npm/index.ts => core/src/npm.ts} (95%) delete mode 100644 packages/opencode/src/npm/config.ts delete mode 100644 packages/opencode/src/npmcli-config.d.ts diff --git a/bun.lock b/bun.lock index 06a414a21..1d2e4462f 100644 --- a/bun.lock +++ b/bun.lock @@ -199,6 +199,8 @@ "dependencies": { "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", + "@npmcli/arborist": "9.4.0", + "@npmcli/config": "10.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", @@ -208,6 +210,8 @@ "glob": "13.0.5", "mime-types": "3.0.2", "minimatch": "10.2.5", + "npm-package-arg": "13.0.2", + "semver": "^7.6.3", "xdg-basedir": "5.1.0", "zod": "catalog:", }, @@ -215,6 +219,9 @@ "@tsconfig/bun": "catalog:", "@types/bun": "catalog:", "@types/cross-spawn": "catalog:", + "@types/npm-package-arg": "6.1.4", + "@types/npmcli__arborist": "6.3.3", + "@types/semver": "catalog:", }, }, "packages/desktop": { @@ -380,8 +387,6 @@ "@hono/zod-validator": "catalog:", "@lydell/node-pty": "catalog:", "@modelcontextprotocol/sdk": "1.27.1", - "@npmcli/arborist": "9.4.0", - "@npmcli/config": "10.8.1", "@octokit/graphql": "9.0.2", "@octokit/rest": "catalog:", "@openauthjs/openauth": "catalog:", @@ -470,7 +475,6 @@ "@types/cross-spawn": "catalog:", "@types/mime-types": "3.0.1", "@types/npm-package-arg": "6.1.4", - "@types/npmcli__arborist": "6.3.3", "@types/semver": "^7.5.8", "@types/turndown": "5.0.5", "@types/which": "3.0.4", @@ -2847,7 +2851,7 @@ "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], + "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], "compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="], @@ -4005,7 +4009,7 @@ "native-duplexpair": ["native-duplexpair@1.0.0", "", {}, "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA=="], - "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], @@ -5515,8 +5519,6 @@ "@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], - "@npmcli/arborist/common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], - "@npmcli/query/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], "@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], @@ -5709,6 +5711,8 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.93", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.69", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@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-hcXDU8QDwpAzLVTuY932TQVlIij9+iaVTxc5mPGY6yb//JMAAC5hMVhg93IrxlrxWLvMgjezNgoZGwquR+SGnw=="], "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="], @@ -5739,6 +5743,8 @@ "astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="], + "astro/common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], + "astro/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="], "astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="], @@ -5893,8 +5899,6 @@ "log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="], @@ -6881,8 +6885,6 @@ "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], - "@electron/rebuild/node-gyp/make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "@electron/rebuild/node-gyp/make-fetch-happen/ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], "@electron/rebuild/node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], @@ -6947,8 +6949,6 @@ "@jsx-email/cli/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], - "@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], "@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], @@ -7147,8 +7147,6 @@ "js-beautify/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], "pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], diff --git a/packages/core/package.json b/packages/core/package.json index bd826de35..62d56908c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -19,11 +19,16 @@ "devDependencies": { "@tsconfig/bun": "catalog:", "@types/bun": "catalog:", - "@types/cross-spawn": "catalog:" + "@types/cross-spawn": "catalog:", + "@types/npm-package-arg": "6.1.4", + "@types/npmcli__arborist": "6.3.3", + "@types/semver": "catalog:" }, "dependencies": { "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", + "@npmcli/arborist": "9.4.0", + "@npmcli/config": "10.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", @@ -33,6 +38,8 @@ "glob": "13.0.5", "mime-types": "3.0.2", "minimatch": "10.2.5", + "npm-package-arg": "13.0.2", + "semver": "^7.6.3", "xdg-basedir": "5.1.0", "zod": "catalog:" }, diff --git a/packages/opencode/src/npm/index.ts b/packages/core/src/npm.ts similarity index 95% rename from packages/opencode/src/npm/index.ts rename to packages/core/src/npm.ts index a7538b9c3..a52e0a9a5 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/core/src/npm.ts @@ -1,20 +1,22 @@ -export * as Npm from "." +export * as Npm from "./npm" import path from "path" import { fileURLToPath } from "url" import npa from "npm-package-arg" import semver from "semver" +// @ts-expect-error npm does not publish types for this internal config API. import Config from "@npmcli/config" +// @ts-expect-error npm does not publish types for this internal config API. import { definitions, flatten, nerfDarts, shorthands } from "@npmcli/config/lib/definitions/index.js" import { Effect, Schema, Context, Layer, Option, FileSystem, Stream } from "effect" import { NodeFileSystem } from "@effect/platform-node" -import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { Global } from "@opencode-ai/core/global" -import { EffectFlock } from "@opencode-ai/core/util/effect-flock" -import { makeRuntime } from "@opencode-ai/core/effect/runtime" +import { AppFileSystem } from "./filesystem" +import { Global } from "./global" +import { EffectFlock } from "./util/effect-flock" +import { makeRuntime } from "./effect/runtime" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { CrossSpawnSpawner } from "./cross-spawn-spawner" export class InstallFailedError extends Schema.TaggedErrorClass()("NpmInstallFailedError", { add: Schema.Array(Schema.String).pipe(Schema.optional), @@ -45,7 +47,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/Npm") {} const illegal = process.platform === "win32" ? new Set(["<", ">", ":", '"', "|", "?", "*"]) : undefined -const npmPath = fileURLToPath(new URL("../..", import.meta.url)) +const npmPath = fileURLToPath(new URL("..", import.meta.url)) export function sanitize(pkg: string) { if (!illegal) return pkg diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 1c60e58a8..98a707f4b 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -61,7 +61,6 @@ "@types/cross-spawn": "catalog:", "@types/mime-types": "3.0.1", "@types/npm-package-arg": "6.1.4", - "@types/npmcli__arborist": "6.3.3", "@types/semver": "^7.5.8", "@types/turndown": "5.0.5", "@types/which": "3.0.4", @@ -110,8 +109,6 @@ "@hono/zod-validator": "catalog:", "@lydell/node-pty": "catalog:", "@modelcontextprotocol/sdk": "1.27.1", - "@npmcli/arborist": "9.4.0", - "@npmcli/config": "10.8.1", "@octokit/graphql": "9.0.2", "@octokit/rest": "catalog:", "@openauthjs/openauth": "catalog:", diff --git a/packages/opencode/src/cli/cmd/tui/config/tui.ts b/packages/opencode/src/cli/cmd/tui/config/tui.ts index b55e5807e..3f99e6d2f 100644 --- a/packages/opencode/src/cli/cmd/tui/config/tui.ts +++ b/packages/opencode/src/cli/cmd/tui/config/tui.ts @@ -18,7 +18,7 @@ import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/instal import { makeRuntime } from "@opencode-ai/core/effect/runtime" import { Filesystem, Log } from "@/util" import { ConfigVariable } from "@/config/variable" -import { Npm } from "@/npm" +import { Npm } from "@opencode-ai/core/npm" const log = Log.create({ service: "tui.config" }) diff --git a/packages/opencode/src/cli/cmd/tui/layer.ts b/packages/opencode/src/cli/cmd/tui/layer.ts index 785455334..a0c19e46d 100644 --- a/packages/opencode/src/cli/cmd/tui/layer.ts +++ b/packages/opencode/src/cli/cmd/tui/layer.ts @@ -1,6 +1,6 @@ import { Layer } from "effect" import { TuiConfig } from "./config/tui" -import { Npm } from "@/npm" +import { Npm } from "@opencode-ai/core/npm" import { Observability } from "@opencode-ai/core/effect/observability" export const CliLayer = Observability.layer.pipe(Layer.merge(TuiConfig.layer), Layer.provide(Npm.defaultLayer)) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index ddd31a3fc..6173b2fb6 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -42,7 +42,7 @@ import { ConfigProvider } from "./provider" import { ConfigServer } from "./server" import { ConfigSkills } from "./skills" import { ConfigVariable } from "./variable" -import { Npm } from "@/npm" +import { Npm } from "@opencode-ai/core/npm" const log = Log.create({ service: "config" }) diff --git a/packages/opencode/src/effect/app-runtime.ts b/packages/opencode/src/effect/app-runtime.ts index b4bdbfca4..fcf64b9d2 100644 --- a/packages/opencode/src/effect/app-runtime.ts +++ b/packages/opencode/src/effect/app-runtime.ts @@ -46,7 +46,7 @@ import { Pty } from "@/pty" import { Installation } from "@/installation" import { ShareNext } from "@/share" import { SessionShare } from "@/share" -import { Npm } from "@/npm" +import { Npm } from "@opencode-ai/core/npm" import { memoMap } from "@opencode-ai/core/effect/memo-map" export const AppLayer = Layer.mergeAll( diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index eefafd575..82666b799 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -1,4 +1,4 @@ -import { Npm } from "../npm" +import { Npm } from "@opencode-ai/core/npm" import type { InstanceContext } from "../project/instance" import { Filesystem } from "../util" import { Process } from "../util" diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 14b674a98..32a5239be 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -13,7 +13,7 @@ import { Process } from "../util" import { which } from "../util/which" import { Module } from "@opencode-ai/core/util/module" import { spawn } from "./launch" -import { Npm } from "../npm" +import { Npm } from "@opencode-ai/core/npm" const log = Log.create({ service: "lsp.server" }) const pathExists = async (p: string) => diff --git a/packages/opencode/src/npm/config.ts b/packages/opencode/src/npm/config.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/opencode/src/npmcli-config.d.ts b/packages/opencode/src/npmcli-config.d.ts deleted file mode 100644 index c9b20517a..000000000 --- a/packages/opencode/src/npmcli-config.d.ts +++ /dev/null @@ -1,43 +0,0 @@ -declare module "@npmcli/config" { - type Data = Record - type Where = "default" | "builtin" | "global" | "user" | "project" | "env" | "cli" - - namespace Config { - interface Options { - definitions: Data - shorthands: Record - npmPath: string - flatten?: (input: Data, flat?: Data) => Data - nerfDarts?: string[] - argv?: string[] - cwd?: string - env?: NodeJS.ProcessEnv - execPath?: string - platform?: NodeJS.Platform - warn?: boolean - } - } - - class Config { - constructor(input: Config.Options) - - readonly data: Map - readonly flat: Data - - load(): Promise - } - - export = Config -} - -declare module "@npmcli/config/lib/definitions" { - export const definitions: Record - export const shorthands: Record - export const flatten: (input: Record, flat?: Record) => Record - export const nerfDarts: string[] - export const proxyEnv: string[] -} - -declare module "@npmcli/config/lib/definitions/index.js" { - export * from "@npmcli/config/lib/definitions" -} diff --git a/packages/opencode/src/plugin/shared.ts b/packages/opencode/src/plugin/shared.ts index ca821216d..a930d5b26 100644 --- a/packages/opencode/src/plugin/shared.ts +++ b/packages/opencode/src/plugin/shared.ts @@ -4,7 +4,7 @@ import npa from "npm-package-arg" import semver from "semver" import { Filesystem } from "@/util" import { isRecord } from "@/util/record" -import { Npm } from "@/npm" +import { Npm } from "@opencode-ai/core/npm" // Old npm package names for plugins that are now built-in export const DEPRECATED_PLUGIN_PACKAGES = ["opencode-openai-codex-auth", "opencode-copilot-auth"] diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index c2439d04f..9aa1b6304 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -4,7 +4,7 @@ import { Config } from "../config" import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda" import { NoSuchModelError, type Provider as SDK } from "ai" import { Log } from "../util" -import { Npm } from "../npm" +import { Npm } from "@opencode-ai/core/npm" import { Hash } from "@opencode-ai/core/util/hash" import { Plugin } from "../plugin" import { type LanguageModelV3 } from "@ai-sdk/provider" diff --git a/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts b/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts index 74236afae..66858e2d0 100644 --- a/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts +++ b/packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts @@ -5,7 +5,7 @@ import { pathToFileURL } from "url" import { tmpdir } from "../../fixture/fixture" import { createTuiPluginApi } from "../../fixture/tui-plugin" import { TuiConfig } from "../../../src/cli/cmd/tui/config/tui" -import { Npm } from "../../../src/npm" +import { Npm } from "@opencode-ai/core/npm" const { TuiPluginRuntime } = await import("../../../src/cli/cmd/tui/plugin/runtime") diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index f6fb04ea0..324914e6d 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -27,7 +27,7 @@ import { Global } from "@opencode-ai/core/global" import { ProjectID } from "../../src/project/schema" import { Filesystem } from "../../src/util" import { ConfigPlugin } from "@/config/plugin" -import { Npm } from "@/npm" +import { Npm } from "@opencode-ai/core/npm" const emptyAccount = Layer.mock(Account.Service)({ active: () => Effect.succeed(Option.none()), diff --git a/packages/opencode/test/npm.test.ts b/packages/opencode/test/npm.test.ts index 09fa6b351..d5b93a83c 100644 --- a/packages/opencode/test/npm.test.ts +++ b/packages/opencode/test/npm.test.ts @@ -7,7 +7,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Global } from "@opencode-ai/core/global" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { Npm } from "../src/npm" +import { Npm } from "@opencode-ai/core/npm" import { tmpdir } from "./fixture/fixture" const win = process.platform === "win32" diff --git a/packages/opencode/test/plugin/loader-shared.test.ts b/packages/opencode/test/plugin/loader-shared.test.ts index 83e9d71b4..88b3bf343 100644 --- a/packages/opencode/test/plugin/loader-shared.test.ts +++ b/packages/opencode/test/plugin/loader-shared.test.ts @@ -13,7 +13,7 @@ const { Plugin } = await import("../../src/plugin/index") const { PluginLoader } = await import("../../src/plugin/loader") const { readPackageThemes } = await import("../../src/plugin/shared") const { Instance } = await import("../../src/project/instance") -const { Npm } = await import("../../src/npm") +const { Npm } = await import("@opencode-ai/core/npm") afterAll(() => { if (disableDefault === undefined) { From 95d4bb213018f9c184cc97c34000b3f5a99fae5a Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 14:46:06 -0400 Subject: [PATCH 176/426] feat(httpapi): bridge experimental read endpoints (#24365) --- packages/opencode/specs/effect/http-api.md | 25 ++- packages/opencode/src/mcp/index.ts | 20 +-- .../server/routes/instance/experimental.ts | 2 +- .../routes/instance/httpapi/experimental.ts | 159 ++++++++++++++++++ .../server/routes/instance/httpapi/server.ts | 2 + .../src/server/routes/instance/index.ts | 5 + packages/opencode/src/tool/bash.ts | 4 +- .../test/server/httpapi-experimental.test.ts | 66 ++++++++ 8 files changed, 270 insertions(+), 13 deletions(-) create mode 100644 packages/opencode/src/server/routes/instance/httpapi/experimental.ts create mode 100644 packages/opencode/test/server/httpapi-experimental.test.ts diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 948389223..6d0381d2d 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -30,6 +30,29 @@ Plan for replacing instance Hono route implementations with Effect `HttpApi` whi - Regenerate the SDK after schema or OpenAPI-affecting changes and verify the diff is expected. - Do not delete a Hono route until the SDK/OpenAPI pipeline no longer depends on its Hono `describeRoute` entry. +## Route Slice Checklist + +Use this checklist for each small HttpApi migration PR: + +1. Read the legacy Hono route and copy behavior exactly, including default values, headers, operation IDs, response schemas, and status codes. +2. Put the new `HttpApiGroup`, route paths, DTO schemas, and handlers in `src/server/routes/instance/httpapi/*`. +3. Mount the new paths in `src/server/routes/instance/index.ts` only inside the `OPENCODE_EXPERIMENTAL_HTTPAPI` block. +4. Use `InstanceState.context` / `InstanceState.directory` inside HttpApi handlers instead of `Instance.directory`, `Instance.worktree`, or `Instance.project` ALS globals. +5. Reuse existing services directly. If a service returns plain objects, use `Schema.Struct`; use `Schema.Class` only when handlers return actual class instances. +6. Keep legacy Hono routes and `.zod` compatibility in place for SDK/OpenAPI generation. +7. Add tests that hit the Hono-mounted bridge via `InstanceRoutes`, not only the raw `HttpApi` web handler, when the route depends on auth or instance context. +8. Run `bun typecheck` from `packages/opencode`, relevant `bun run test:ci ...` tests from `packages/opencode`, and `./packages/sdk/js/script/build.ts` from the repo root. + +## Experimental Read Slice Guidance + +For the experimental route group, port read-only JSON routes before mutations: + +- Good first batch: `GET /console`, `GET /console/orgs`, `GET /tool/ids`, `GET /resource`. +- Consider `GET /worktree` only if the handler uses `InstanceState.context` instead of `Instance.project`. +- Defer `POST /console/switch`, worktree create/remove/reset, and `GET /session` to separate PRs because they mutate state or have broader pagination/session behavior. +- Preserve response headers such as pagination cursors if a route is ported. +- If SDK generation changes, explain whether it is a semantic contract change or a generator-equivalent type normalization. + ## Schema Notes - Use `Schema.Struct(...).annotate({ identifier })` for named OpenAPI refs when handlers return plain objects. @@ -141,7 +164,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `mcp` | `bridged` partial | status only | | `workspace` | `bridged` | list, get, enter | | top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter | -| experimental JSON routes | `next/later` | console, tool, worktree, resource, global session list | +| experimental JSON routes | `bridged` partial | console reads, tool ids, resource list; worktree and global session list remain later | | `session` | `later/special` | large stateful surface plus streaming | | `sync` | `later` | process/control side effects | | `event` | `special` | SSE | diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index c2479372d..533466925 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -36,16 +36,16 @@ import { withStatics } from "@/util/schema" const log = Log.create({ service: "mcp" }) const DEFAULT_TIMEOUT = 30_000 -export const Resource = z - .object({ - name: z.string(), - uri: z.string(), - description: z.string().optional(), - mimeType: z.string().optional(), - client: z.string(), - }) - .meta({ ref: "McpResource" }) -export type Resource = z.infer +export const Resource = Schema.Struct({ + name: Schema.String, + uri: Schema.String, + description: Schema.optional(Schema.String), + mimeType: Schema.optional(Schema.String), + client: Schema.String, +}) + .annotate({ identifier: "McpResource" }) + .pipe(withStatics((s) => ({ zod: effectZod(s) }))) +export type Resource = Schema.Schema.Type export const ToolsChanged = BusEvent.define( "mcp.tools.changed", diff --git a/packages/opencode/src/server/routes/instance/experimental.ts b/packages/opencode/src/server/routes/instance/experimental.ts index f13003cb4..0936f6252 100644 --- a/packages/opencode/src/server/routes/instance/experimental.ts +++ b/packages/opencode/src/server/routes/instance/experimental.ts @@ -394,7 +394,7 @@ export const ExperimentalRoutes = lazy(() => description: "MCP resources", content: { "application/json": { - schema: resolver(z.record(z.string(), MCP.Resource)), + schema: resolver(z.record(z.string(), MCP.Resource.zod)), }, }, }, diff --git a/packages/opencode/src/server/routes/instance/httpapi/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts new file mode 100644 index 000000000..993971202 --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts @@ -0,0 +1,159 @@ +import { Account } from "@/account/account" +import { Config } from "@/config" +import { MCP } from "@/mcp" +import { ToolRegistry } from "@/tool" +import { Effect, Layer, Option, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" + +const ConsoleStateResponse = Schema.Struct({ + consoleManagedProviders: Schema.mutable(Schema.Array(Schema.String)), + activeOrgName: Schema.optionalKey(Schema.String), + switchableOrgCount: Schema.Number, +}).annotate({ identifier: "ConsoleState" }) + +const ConsoleOrgOption = Schema.Struct({ + accountID: Schema.String, + accountEmail: Schema.String, + accountUrl: Schema.String, + orgID: Schema.String, + orgName: Schema.String, + active: Schema.Boolean, +}).annotate({ identifier: "ConsoleOrgOption" }) + +const ConsoleOrgList = Schema.Struct({ + orgs: Schema.Array(ConsoleOrgOption), +}).annotate({ identifier: "ConsoleOrgList" }) + +const ToolIDs = Schema.Array(Schema.String).annotate({ identifier: "ToolIDs" }) + +export const ExperimentalPaths = { + console: "/experimental/console", + consoleOrgs: "/experimental/console/orgs", + toolIDs: "/experimental/tool/ids", + resource: "/experimental/resource", +} as const + +export const ExperimentalApi = HttpApi.make("experimental") + .add( + HttpApiGroup.make("experimental") + .add( + HttpApiEndpoint.get("console", ExperimentalPaths.console, { + success: ConsoleStateResponse, + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.console.get", + summary: "Get active Console provider metadata", + description: "Get the active Console org name and the set of provider IDs managed by that Console org.", + }), + ), + HttpApiEndpoint.get("consoleOrgs", ExperimentalPaths.consoleOrgs, { + success: ConsoleOrgList, + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.console.listOrgs", + summary: "List switchable Console orgs", + description: "Get the available Console orgs across logged-in accounts, including the current active org.", + }), + ), + HttpApiEndpoint.get("toolIDs", ExperimentalPaths.toolIDs, { + success: ToolIDs, + }).annotateMerge( + OpenApi.annotations({ + identifier: "tool.ids", + summary: "List tool IDs", + description: + "Get a list of all available tool IDs, including both built-in tools and dynamically registered tools.", + }), + ), + HttpApiEndpoint.get("resource", ExperimentalPaths.resource, { + success: Schema.Record(Schema.String, MCP.Resource), + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.resource.list", + summary: "Get MCP resources", + description: "Get all available MCP resources from connected servers. Optionally filter by name.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "experimental", + description: "Experimental HttpApi read-only routes.", + }), + ) + .middleware(Authorization), + ) + .annotateMerge( + OpenApi.annotations({ + title: "opencode experimental HttpApi", + version: "0.0.1", + description: "Experimental HttpApi surface for selected instance routes.", + }), + ) + +export const experimentalHandlers = Layer.unwrap( + Effect.gen(function* () { + const account = yield* Account.Service + const config = yield* Config.Service + const mcp = yield* MCP.Service + const registry = yield* ToolRegistry.Service + + const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () { + const [state, groups] = yield* Effect.all( + [config.getConsoleState(), account.orgsByAccount().pipe(Effect.orDie)], + { + concurrency: "unbounded", + }, + ) + return { + consoleManagedProviders: state.consoleManagedProviders, + ...(state.activeOrgName ? { activeOrgName: state.activeOrgName } : {}), + switchableOrgCount: groups.reduce((count, group) => count + group.orgs.length, 0), + } + }) + + const listConsoleOrgs = Effect.fn("ExperimentalHttpApi.consoleOrgs")(function* () { + const [groups, active] = yield* Effect.all( + [account.orgsByAccount().pipe(Effect.orDie), account.active().pipe(Effect.orDie)], + { + concurrency: "unbounded", + }, + ) + const info = Option.getOrUndefined(active) + return { + orgs: groups.flatMap((group) => + group.orgs.map((org) => ({ + accountID: group.account.id, + accountEmail: group.account.email, + accountUrl: group.account.url, + orgID: org.id, + orgName: org.name, + active: !!info && info.id === group.account.id && info.active_org_id === org.id, + })), + ), + } + }) + + const toolIDs = Effect.fn("ExperimentalHttpApi.toolIDs")(function* () { + return yield* registry.ids() + }) + + const resource = Effect.fn("ExperimentalHttpApi.resource")(function* () { + return yield* mcp.resources() + }) + + return HttpApiBuilder.group(ExperimentalApi, "experimental", (handlers) => + handlers + .handle("console", getConsole) + .handle("consoleOrgs", listConsoleOrgs) + .handle("toolIDs", toolIDs) + .handle("resource", resource), + ) + }), +).pipe( + Layer.provide(Account.defaultLayer), + Layer.provide(Config.defaultLayer), + Layer.provide(MCP.defaultLayer), + Layer.provide(ToolRegistry.defaultLayer), +) diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index 17c3ba4b4..77a2832ce 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -11,6 +11,7 @@ import { Filesystem } from "@/util" import { authorizationLayer } from "./auth" import { ConfigApi, configHandlers } from "./config" import { FileApi, fileHandlers } from "./file" +import { ExperimentalApi, experimentalHandlers } from "./experimental" import { InstanceApi, instanceHandlers } from "./instance" import { McpApi, mcpHandlers } from "./mcp" import { PermissionApi, permissionHandlers } from "./permission" @@ -63,6 +64,7 @@ const instance = HttpRouter.middleware()( export const routes = Layer.mergeAll( HttpApiBuilder.layer(ConfigApi).pipe(Layer.provide(configHandlers)), + HttpApiBuilder.layer(ExperimentalApi).pipe(Layer.provide(experimentalHandlers)), HttpApiBuilder.layer(FileApi).pipe(Layer.provide(fileHandlers)), HttpApiBuilder.layer(InstanceApi).pipe(Layer.provide(instanceHandlers)), HttpApiBuilder.layer(McpApi).pipe(Layer.provide(mcpHandlers)), diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 488e43542..65dd41705 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -16,6 +16,7 @@ import { QuestionRoutes } from "./question" import { PermissionRoutes } from "./permission" import { Flag } from "@opencode-ai/core/flag/flag" import { ExperimentalHttpApiServer } from "./httpapi/server" +import { ExperimentalPaths } from "./httpapi/experimental" import { FilePaths } from "./httpapi/file" import { InstancePaths } from "./httpapi/instance" import { McpPaths } from "./httpapi/mcp" @@ -45,6 +46,10 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.post("/permission/:requestID/reply", (c) => handler(c.req.raw, context)) app.get("/config", (c) => handler(c.req.raw, context)) app.get("/config/providers", (c) => handler(c.req.raw, context)) + app.get(ExperimentalPaths.console, (c) => handler(c.req.raw, context)) + app.get(ExperimentalPaths.consoleOrgs, (c) => handler(c.req.raw, context)) + app.get(ExperimentalPaths.toolIDs, (c) => handler(c.req.raw, context)) + app.get(ExperimentalPaths.resource, (c) => handler(c.req.raw, context)) app.get("/provider", (c) => handler(c.req.raw, context)) app.get("/provider/auth", (c) => handler(c.req.raw, context)) app.post("/provider/:providerID/oauth/authorize", (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index eeba5ebd6..a27d7c5ec 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -20,6 +20,7 @@ import { Plugin } from "@/plugin" import { Effect, Stream } from "effect" import { ChildProcess } from "effect/unstable/process" import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" +import { InstanceState } from "@/effect" const MAX_METADATA_LENGTH = 30_000 const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000 @@ -575,9 +576,10 @@ export const BashTool = Tool.define( log.info("bash tool using shell", { shell }) const limits = yield* trunc.limits() + const instance = yield* InstanceState.context return { - description: DESCRIPTION.replaceAll("${directory}", Instance.directory) + description: DESCRIPTION.replaceAll("${directory}", instance.directory) .replaceAll("${os}", process.platform) .replaceAll("${shell}", name) .replaceAll("${chaining}", chain) diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts new file mode 100644 index 000000000..00d1fefb8 --- /dev/null +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -0,0 +1,66 @@ +import { afterEach, describe, expect, test } from "bun:test" +import type { UpgradeWebSocket } from "hono/ws" +import { Flag } from "@opencode-ai/core/flag/flag" +import { Instance } from "../../src/project/instance" +import { InstanceRoutes } from "../../src/server/routes/instance" +import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/experimental" +import { Log } from "../../src/util" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" + +void Log.init({ print: false }) + +const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI +const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket + +function app() { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true + return InstanceRoutes(websocket) +} + +afterEach(async () => { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original + await Instance.disposeAll() + await resetDatabase() +}) + +describe("experimental HttpApi", () => { + test("serves read-only experimental endpoints through Hono bridge", async () => { + await using tmp = await tmpdir({ + config: { + formatter: false, + lsp: false, + mcp: { + demo: { + type: "local", + command: ["echo", "demo"], + enabled: false, + }, + }, + }, + }) + + const headers = { "x-opencode-directory": tmp.path } + const [consoleState, consoleOrgs, toolIDs, resources] = await Promise.all([ + app().request(ExperimentalPaths.console, { headers }), + app().request(ExperimentalPaths.consoleOrgs, { headers }), + app().request(ExperimentalPaths.toolIDs, { headers }), + app().request(ExperimentalPaths.resource, { headers }), + ]) + + expect(consoleState.status).toBe(200) + expect(await consoleState.json()).toEqual({ + consoleManagedProviders: [], + switchableOrgCount: 0, + }) + + expect(consoleOrgs.status).toBe(200) + expect(await consoleOrgs.json()).toEqual({ orgs: [] }) + + expect(toolIDs.status).toBe(200) + expect(await toolIDs.json()).toContain("bash") + + expect(resources.status).toBe(200) + expect(await resources.json()).toEqual({}) + }) +}) From 3b74077437f15d6ea3130ad2255295157a934f2f Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 18:47:06 +0000 Subject: [PATCH 177/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 6d0381d2d..e32a1dd7e 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -153,23 +153,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------ | ----------------- | ------------------------------------------------------ | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` partial | reads only; mutation remains Hono | -| `project` | `bridged` partial | reads only; git-init remains Hono | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` partial | status only | -| `workspace` | `bridged` | list, get, enter | -| top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter | +| Area | Status | Notes | +| ------------------------ | ----------------- | ------------------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` partial | reads only; mutation remains Hono | +| `project` | `bridged` partial | reads only; git-init remains Hono | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` partial | status only | +| `workspace` | `bridged` | list, get, enter | +| top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter | | experimental JSON routes | `bridged` partial | console reads, tool ids, resource list; worktree and global session list remain later | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `later` | process/control side effects | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `later` | process/control side effects | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Next PRs From 60fa708f0b4490c49cddd518f9dd7b7471181379 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 18:49:27 +0000 Subject: [PATCH 178/426] 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 037e9c57e..84fb57a1c 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-0w22pAViYEcELJpOrIVCjTQ73fnsSaJxb75heAIUdYE=", - "aarch64-linux": "sha256-z1fpZ9HQU9n6W5xhKzuUduwQUJa/nrj9WFZdBLL/e/8=", - "aarch64-darwin": "sha256-y5AraTdY2uDTltjQFlHjMoMo6FICgQNKSunIOnQAXnY=", - "x86_64-darwin": "sha256-KQF6dJNQ587xp5h9ET+tLni9dLNwYnzxg2DX+KWfpoE=" + "x86_64-linux": "sha256-LpzWEZzURUEj7fcHGvh33gM7D9GNPE+XIvU0/hmdcQM=", + "aarch64-linux": "sha256-0zdO3zuj6g9cMZFEOsvQJcKKcPjGVZJ2DkJdDcb2VCM=", + "aarch64-darwin": "sha256-dmT8R9Pmzh5tjO8NCCCtENiQpJQeifQpVdhaty1MXOs=", + "x86_64-darwin": "sha256-Q6rAQRoC6WaMAQl++YHAZmbNuO303cWgGaYzXaRlzy4=" } } From b749866f0b21328919e0c525109c5b2f3e4e0e24 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 14:55:29 -0400 Subject: [PATCH 179/426] feat(httpapi): bridge worktree read endpoint (#24366) --- packages/opencode/specs/effect/http-api.md | 2 +- .../routes/instance/httpapi/experimental.ts | 22 +++++++++++++++++++ .../src/server/routes/instance/index.ts | 1 + .../test/server/httpapi-experimental.test.ts | 6 ++++- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index e32a1dd7e..3e4f30d02 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -164,7 +164,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `mcp` | `bridged` partial | status only | | `workspace` | `bridged` | list, get, enter | | top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter | -| experimental JSON routes | `bridged` partial | console reads, tool ids, resource list; worktree and global session list remain later | +| experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list, resource list; global session list remains later | | `session` | `later/special` | large stateful surface plus streaming | | `sync` | `later` | process/control side effects | | `event` | `special` | SSE | diff --git a/packages/opencode/src/server/routes/instance/httpapi/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts index 993971202..4bd9ba30a 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/experimental.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts @@ -1,6 +1,8 @@ import { Account } from "@/account/account" import { Config } from "@/config" +import { InstanceState } from "@/effect" import { MCP } from "@/mcp" +import { Project } from "@/project" import { ToolRegistry } from "@/tool" import { Effect, Layer, Option, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" @@ -27,10 +29,13 @@ const ConsoleOrgList = Schema.Struct({ const ToolIDs = Schema.Array(Schema.String).annotate({ identifier: "ToolIDs" }) +const WorktreeList = Schema.Array(Schema.String).annotate({ identifier: "WorktreeList" }) + export const ExperimentalPaths = { console: "/experimental/console", consoleOrgs: "/experimental/console/orgs", toolIDs: "/experimental/tool/ids", + worktree: "/experimental/worktree", resource: "/experimental/resource", } as const @@ -66,6 +71,15 @@ export const ExperimentalApi = HttpApi.make("experimental") "Get a list of all available tool IDs, including both built-in tools and dynamically registered tools.", }), ), + HttpApiEndpoint.get("worktree", ExperimentalPaths.worktree, { + success: WorktreeList, + }).annotateMerge( + OpenApi.annotations({ + identifier: "worktree.list", + summary: "List worktrees", + description: "List all sandbox worktrees for the current project.", + }), + ), HttpApiEndpoint.get("resource", ExperimentalPaths.resource, { success: Schema.Record(Schema.String, MCP.Resource), }).annotateMerge( @@ -97,6 +111,7 @@ export const experimentalHandlers = Layer.unwrap( const account = yield* Account.Service const config = yield* Config.Service const mcp = yield* MCP.Service + const project = yield* Project.Service const registry = yield* ToolRegistry.Service const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () { @@ -139,6 +154,11 @@ export const experimentalHandlers = Layer.unwrap( return yield* registry.ids() }) + const worktree = Effect.fn("ExperimentalHttpApi.worktree")(function* () { + const ctx = yield* InstanceState.context + return yield* project.sandboxes(ctx.project.id) + }) + const resource = Effect.fn("ExperimentalHttpApi.resource")(function* () { return yield* mcp.resources() }) @@ -148,6 +168,7 @@ export const experimentalHandlers = Layer.unwrap( .handle("console", getConsole) .handle("consoleOrgs", listConsoleOrgs) .handle("toolIDs", toolIDs) + .handle("worktree", worktree) .handle("resource", resource), ) }), @@ -155,5 +176,6 @@ export const experimentalHandlers = Layer.unwrap( Layer.provide(Account.defaultLayer), Layer.provide(Config.defaultLayer), Layer.provide(MCP.defaultLayer), + Layer.provide(Project.defaultLayer), Layer.provide(ToolRegistry.defaultLayer), ) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 65dd41705..8e4c497bd 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -49,6 +49,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get(ExperimentalPaths.console, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.consoleOrgs, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.toolIDs, (c) => handler(c.req.raw, context)) + app.get(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.resource, (c) => handler(c.req.raw, context)) app.get("/provider", (c) => handler(c.req.raw, context)) app.get("/provider/auth", (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts index 00d1fefb8..38f43e68f 100644 --- a/packages/opencode/test/server/httpapi-experimental.test.ts +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -41,10 +41,11 @@ describe("experimental HttpApi", () => { }) const headers = { "x-opencode-directory": tmp.path } - const [consoleState, consoleOrgs, toolIDs, resources] = await Promise.all([ + const [consoleState, consoleOrgs, toolIDs, worktrees, resources] = await Promise.all([ app().request(ExperimentalPaths.console, { headers }), app().request(ExperimentalPaths.consoleOrgs, { headers }), app().request(ExperimentalPaths.toolIDs, { headers }), + app().request(ExperimentalPaths.worktree, { headers }), app().request(ExperimentalPaths.resource, { headers }), ]) @@ -60,6 +61,9 @@ describe("experimental HttpApi", () => { expect(toolIDs.status).toBe(200) expect(await toolIDs.json()).toContain("bash") + expect(worktrees.status).toBe(200) + expect(await worktrees.json()).toEqual([]) + expect(resources.status).toBe(200) expect(await resources.json()).toEqual({}) }) From 9af46df5356cac29ee3987dd85113e7a59222b08 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 18:56:31 +0000 Subject: [PATCH 180/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 3e4f30d02..a8f9f1e0a 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -153,23 +153,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------ | ----------------- | ------------------------------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` partial | reads only; mutation remains Hono | -| `project` | `bridged` partial | reads only; git-init remains Hono | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` partial | status only | -| `workspace` | `bridged` | list, get, enter | -| top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter | +| Area | Status | Notes | +| ------------------------ | ----------------- | ---------------------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` partial | reads only; mutation remains Hono | +| `project` | `bridged` partial | reads only; git-init remains Hono | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` partial | status only | +| `workspace` | `bridged` | list, get, enter | +| top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter | | experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list, resource list; global session list remains later | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `later` | process/control side effects | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `later` | process/control side effects | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Next PRs From cd64b670388f45dfddad7fe543ab9c0ff490b81c Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 15:01:41 -0400 Subject: [PATCH 181/426] feat(tui): show /connect tip when user has no models configured (#24014) --- .../cli/cmd/tui/feature-plugins/home/tips-view.tsx | 11 +++++++---- .../src/cli/cmd/tui/feature-plugins/home/tips.tsx | 13 +++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx index 1a9d907bb..c7a7b211f 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx @@ -1,4 +1,4 @@ -import { For } from "solid-js" +import { createMemo, For } from "solid-js" import { DEFAULT_THEMES, useTheme } from "@tui/context/theme" const themeCount = Object.keys(DEFAULT_THEMES).length @@ -30,9 +30,12 @@ function parse(tip: string): TipPart[] { return parts } -export function Tips() { +const NO_MODELS_TIP = "Run {highlight}/connect{/highlight} to add an AI provider and start coding" + +export function Tips(props: { connected?: boolean }) { const theme = useTheme().theme - const parts = parse(TIPS[Math.floor(Math.random() * TIPS.length)]) + const randomTip = TIPS[Math.floor(Math.random() * TIPS.length)] + const parts = createMemo(() => parse(props.connected === false ? NO_MODELS_TIP : randomTip)) return ( @@ -40,7 +43,7 @@ export function Tips() { ● Tip{" "} - + {(part) => {part.text}} diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips.tsx index c0e02f74a..26c03ee34 100644 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips.tsx +++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips.tsx @@ -4,11 +4,11 @@ import { Tips } from "./tips-view" const id = "internal:home-tips" -function View(props: { show: boolean }) { +function View(props: { show: boolean; connected: boolean }) { return ( - + ) @@ -35,8 +35,13 @@ const tui: TuiPlugin = async (api) => { home_bottom() { const hidden = createMemo(() => api.kv.get("tips_hidden", false)) const first = createMemo(() => api.state.session.count() === 0) - const show = createMemo(() => !first() && !hidden()) - return + const connected = createMemo(() => + api.state.provider.some( + (item) => item.id !== "opencode" || Object.values(item.models).some((model) => model.cost?.input !== 0), + ), + ) + const show = createMemo(() => (!first() || !connected()) && !hidden()) + return }, }, }) From b4f4134e8100bbe37fe6ad0d8bb2520599f88271 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 15:24:07 -0400 Subject: [PATCH 182/426] feat(httpapi): bridge instance dispose endpoint (#24368) --- packages/opencode/specs/effect/http-api.md | 2 +- .../routes/instance/httpapi/instance.ts | 17 +++++++++++++ .../routes/instance/httpapi/lifecycle.ts | 24 +++++++++++++++++++ .../server/routes/instance/httpapi/server.ts | 2 ++ .../src/server/routes/instance/index.ts | 1 + .../test/server/httpapi-instance.test.ts | 23 ++++++++++++++++++ 6 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index a8f9f1e0a..cbd4987cc 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -163,7 +163,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `file` | `bridged` partial | find text/file/symbol, list/content/status | | `mcp` | `bridged` partial | status only | | `workspace` | `bridged` | list, get, enter | -| top-level instance reads | `bridged` | path, vcs, command, agent, skill, lsp, formatter | +| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | | experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list, resource list; global session list remains later | | `session` | `later/special` | large stateful surface plus streaming | | `sync` | `later` | process/control side effects | diff --git a/packages/opencode/src/server/routes/instance/httpapi/instance.ts b/packages/opencode/src/server/routes/instance/httpapi/instance.ts index 016703e77..8788dee5f 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/instance.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/instance.ts @@ -9,6 +9,7 @@ import * as InstanceState from "@/effect/instance-state" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" +import { markInstanceForDisposal } from "./lifecycle" const PathInfo = Schema.Struct({ home: Schema.String, @@ -23,6 +24,7 @@ const VcsDiffQuery = Schema.Struct({ }) export const InstancePaths = { + dispose: "/instance/dispose", path: "/path", vcs: "/vcs", vcsDiff: "/vcs/diff", @@ -37,6 +39,15 @@ export const InstanceApi = HttpApi.make("instance") .add( HttpApiGroup.make("instance") .add( + HttpApiEndpoint.post("dispose", InstancePaths.dispose, { + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "instance.dispose", + summary: "Dispose instance", + description: "Clean up and dispose the current OpenCode instance, releasing all resources.", + }), + ), HttpApiEndpoint.get("path", InstancePaths.path, { success: PathInfo, }).annotateMerge( @@ -138,6 +149,11 @@ export const instanceHandlers = Layer.unwrap( const skill = yield* Skill.Service const vcs = yield* Vcs.Service + const dispose = Effect.fn("InstanceHttpApi.dispose")(function* () { + yield* markInstanceForDisposal(yield* InstanceState.context) + return true + }) + const getPath = Effect.fn("InstanceHttpApi.path")(function* () { const ctx = yield* InstanceState.context return { @@ -180,6 +196,7 @@ export const instanceHandlers = Layer.unwrap( return HttpApiBuilder.group(InstanceApi, "instance", (handlers) => handlers + .handle("dispose", dispose) .handle("path", getPath) .handle("vcs", getVcs) .handle("vcsDiff", getVcsDiff) diff --git a/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts b/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts new file mode 100644 index 000000000..6b11dffd5 --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts @@ -0,0 +1,24 @@ +import { Instance, type InstanceContext } from "@/project/instance" +import { Effect } from "effect" +import { HttpEffect, HttpMiddleware, HttpServerRequest } from "effect/unstable/http" + +const disposeAfterResponse = new WeakMap() + +export const markInstanceForDisposal = (ctx: InstanceContext) => + HttpEffect.appendPreResponseHandler((request, response) => + Effect.sync(() => { + disposeAfterResponse.set(request.source, ctx) + return response + }), + ) + +export const disposeMiddleware: HttpMiddleware.HttpMiddleware = (effect) => + Effect.gen(function* () { + const response = yield* effect + const request = yield* HttpServerRequest.HttpServerRequest + const ctx = disposeAfterResponse.get(request.source) + if (!ctx) return response + disposeAfterResponse.delete(request.source) + yield* Effect.promise(() => Instance.restore(ctx, () => Instance.dispose())) + return response + }) diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index 77a2832ce..be574c914 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -19,6 +19,7 @@ import { ProjectApi, projectHandlers } from "./project" import { ProviderApi, providerHandlers } from "./provider" import { QuestionApi, questionHandlers } from "./question" import { WorkspaceApi, workspaceHandlers } from "./workspace" +import { disposeMiddleware } from "./lifecycle" import { memoMap } from "@opencode-ai/core/effect/memo-map" const Query = Schema.Struct({ @@ -83,6 +84,7 @@ export const routes = Layer.mergeAll( export const webHandler = lazy(() => HttpRouter.toWebHandler(routes, { memoMap, + middleware: disposeMiddleware, }), ) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 8e4c497bd..2a8fc602a 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -64,6 +64,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get(FilePaths.content, (c) => handler(c.req.raw, context)) app.get(FilePaths.status, (c) => handler(c.req.raw, context)) app.get(InstancePaths.path, (c) => handler(c.req.raw, context)) + app.post(InstancePaths.dispose, (c) => handler(c.req.raw, context)) app.get(InstancePaths.vcs, (c) => handler(c.req.raw, context)) app.get(InstancePaths.vcsDiff, (c) => handler(c.req.raw, context)) app.get(InstancePaths.command, (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/test/server/httpapi-instance.test.ts b/packages/opencode/test/server/httpapi-instance.test.ts index a066e0e92..463dbaa87 100644 --- a/packages/opencode/test/server/httpapi-instance.test.ts +++ b/packages/opencode/test/server/httpapi-instance.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test" import type { UpgradeWebSocket } from "hono/ws" import path from "path" import { Flag } from "@opencode-ai/core/flag/flag" +import { GlobalBus } from "@/bus/global" import { Instance } from "../../src/project/instance" import { InstanceRoutes } from "../../src/server/routes/instance" import { InstancePaths } from "../../src/server/routes/instance/httpapi/instance" @@ -77,4 +78,26 @@ describe("instance HttpApi", () => { expect(formatter.status).toBe(200) expect(await formatter.json()).toEqual([]) }) + + test("serves instance dispose through Hono bridge", async () => { + await using tmp = await tmpdir() + + const disposed = new Promise((resolve) => { + const onEvent = (event: { directory?: string; payload: { type?: string } }) => { + if (event.payload.type !== "server.instance.disposed") return + GlobalBus.off("event", onEvent) + resolve(event.directory) + } + GlobalBus.on("event", onEvent) + }) + + const response = await app().request(InstancePaths.dispose, { + method: "POST", + headers: { "x-opencode-directory": tmp.path }, + }) + + expect(response.status).toBe(200) + expect(await response.json()).toBe(true) + expect(await disposed).toBe(tmp.path) + }) }) From 474024f9e669926b65738a237310c88c3d4adfe3 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 19:25:41 +0000 Subject: [PATCH 183/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index cbd4987cc..c57debb24 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -153,23 +153,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------ | ----------------- | ---------------------------------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` partial | reads only; mutation remains Hono | -| `project` | `bridged` partial | reads only; git-init remains Hono | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` partial | status only | -| `workspace` | `bridged` | list, get, enter | +| Area | Status | Notes | +| ------------------------- | ----------------- | ---------------------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` partial | reads only; mutation remains Hono | +| `project` | `bridged` partial | reads only; git-init remains Hono | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` partial | status only | +| `workspace` | `bridged` | list, get, enter | | top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list, resource list; global session list remains later | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `later` | process/control side effects | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list, resource list; global session list remains later | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `later` | process/control side effects | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Next PRs From a36913022609ec90a26037fa3767cdb60eb49597 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 15:35:15 -0400 Subject: [PATCH 184/426] feat(httpapi): bridge worktree mutations (#24371) --- packages/opencode/specs/effect/http-api.md | 2 +- .../server/routes/instance/experimental.ts | 8 +-- .../routes/instance/httpapi/experimental.ts | 59 +++++++++++++++ .../src/server/routes/instance/index.ts | 3 + packages/opencode/src/worktree/index.ts | 72 +++++++++---------- .../test/server/httpapi-experimental.test.ts | 65 +++++++++++++++++ 6 files changed, 164 insertions(+), 45 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index c57debb24..f9e9948cf 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -164,7 +164,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `mcp` | `bridged` partial | status only | | `workspace` | `bridged` | list, get, enter | | top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list, resource list; global session list remains later | +| experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list/mutations, resource list; global session list remains later | | `session` | `later/special` | large stateful surface plus streaming | | `sync` | `later` | process/control side effects | | `event` | `special` | SSE | diff --git a/packages/opencode/src/server/routes/instance/experimental.ts b/packages/opencode/src/server/routes/instance/experimental.ts index 0936f6252..a407590f2 100644 --- a/packages/opencode/src/server/routes/instance/experimental.ts +++ b/packages/opencode/src/server/routes/instance/experimental.ts @@ -230,14 +230,14 @@ export const ExperimentalRoutes = lazy(() => description: "Worktree created", content: { "application/json": { - schema: resolver(Worktree.Info), + schema: resolver(Worktree.Info.zod), }, }, }, ...errors(400), }, }), - validator("json", Worktree.CreateInput.optional()), + validator("json", Worktree.CreateInput.zod.optional()), async (c) => jsonRequest("ExperimentalRoutes.worktree.create", c, function* () { const body = c.req.valid("json") @@ -286,7 +286,7 @@ export const ExperimentalRoutes = lazy(() => ...errors(400), }, }), - validator("json", Worktree.RemoveInput), + validator("json", Worktree.RemoveInput.zod), async (c) => jsonRequest("ExperimentalRoutes.worktree.remove", c, function* () { const body = c.req.valid("json") @@ -315,7 +315,7 @@ export const ExperimentalRoutes = lazy(() => ...errors(400), }, }), - validator("json", Worktree.ResetInput), + validator("json", Worktree.ResetInput.zod), async (c) => jsonRequest("ExperimentalRoutes.worktree.reset", c, function* () { const body = c.req.valid("json") diff --git a/packages/opencode/src/server/routes/instance/httpapi/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts index 4bd9ba30a..14f54d457 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/experimental.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts @@ -4,6 +4,7 @@ import { InstanceState } from "@/effect" import { MCP } from "@/mcp" import { Project } from "@/project" import { ToolRegistry } from "@/tool" +import { Worktree } from "@/worktree" import { Effect, Layer, Option, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" @@ -36,6 +37,7 @@ export const ExperimentalPaths = { consoleOrgs: "/experimental/console/orgs", toolIDs: "/experimental/tool/ids", worktree: "/experimental/worktree", + worktreeReset: "/experimental/worktree/reset", resource: "/experimental/resource", } as const @@ -80,6 +82,36 @@ export const ExperimentalApi = HttpApi.make("experimental") description: "List all sandbox worktrees for the current project.", }), ), + HttpApiEndpoint.post("worktreeCreate", ExperimentalPaths.worktree, { + payload: Schema.optional(Worktree.CreateInput), + success: Worktree.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "worktree.create", + summary: "Create worktree", + description: "Create a new git worktree for the current project and run any configured startup scripts.", + }), + ), + HttpApiEndpoint.delete("worktreeRemove", ExperimentalPaths.worktree, { + payload: Worktree.RemoveInput, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "worktree.remove", + summary: "Remove worktree", + description: "Remove a git worktree and delete its branch.", + }), + ), + HttpApiEndpoint.post("worktreeReset", ExperimentalPaths.worktreeReset, { + payload: Worktree.ResetInput, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "worktree.reset", + summary: "Reset worktree", + description: "Reset a worktree branch to the primary default branch.", + }), + ), HttpApiEndpoint.get("resource", ExperimentalPaths.resource, { success: Schema.Record(Schema.String, MCP.Resource), }).annotateMerge( @@ -113,6 +145,7 @@ export const experimentalHandlers = Layer.unwrap( const mcp = yield* MCP.Service const project = yield* Project.Service const registry = yield* ToolRegistry.Service + const worktreeSvc = yield* Worktree.Service const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () { const [state, groups] = yield* Effect.all( @@ -159,6 +192,28 @@ export const experimentalHandlers = Layer.unwrap( return yield* project.sandboxes(ctx.project.id) }) + const worktreeCreate = Effect.fn("ExperimentalHttpApi.worktreeCreate")(function* (ctx: { + payload: Worktree.CreateInput | undefined + }) { + return yield* worktreeSvc.create(ctx.payload) + }) + + const worktreeRemove = Effect.fn("ExperimentalHttpApi.worktreeRemove")(function* (input: { + payload: Worktree.RemoveInput + }) { + const ctx = yield* InstanceState.context + yield* worktreeSvc.remove(input.payload) + yield* project.removeSandbox(ctx.project.id, input.payload.directory) + return true + }) + + const worktreeReset = Effect.fn("ExperimentalHttpApi.worktreeReset")(function* (ctx: { + payload: Worktree.ResetInput + }) { + yield* worktreeSvc.reset(ctx.payload) + return true + }) + const resource = Effect.fn("ExperimentalHttpApi.resource")(function* () { return yield* mcp.resources() }) @@ -169,6 +224,9 @@ export const experimentalHandlers = Layer.unwrap( .handle("consoleOrgs", listConsoleOrgs) .handle("toolIDs", toolIDs) .handle("worktree", worktree) + .handle("worktreeCreate", worktreeCreate) + .handle("worktreeRemove", worktreeRemove) + .handle("worktreeReset", worktreeReset) .handle("resource", resource), ) }), @@ -178,4 +236,5 @@ export const experimentalHandlers = Layer.unwrap( Layer.provide(MCP.defaultLayer), Layer.provide(Project.defaultLayer), Layer.provide(ToolRegistry.defaultLayer), + Layer.provide(Worktree.defaultLayer), ) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 2a8fc602a..c006410b8 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -50,6 +50,9 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get(ExperimentalPaths.consoleOrgs, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.toolIDs, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) + app.post(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) + app.delete(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) + app.post(ExperimentalPaths.worktreeReset, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.resource, (c) => handler(c.req.raw, context)) app.get("/provider", (c) => handler(c.req.raw, context)) app.get("/provider/auth", (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index b9c7226b6..8d635e80f 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -20,6 +20,8 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { BootstrapRuntime } from "@/effect/bootstrap-runtime" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { InstanceState } from "@/effect" +import { zod as effectZod } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" const log = Log.create({ service: "worktree" }) @@ -39,48 +41,38 @@ export const Event = { ), } -export const Info = z - .object({ - name: z.string(), - branch: z.string(), - directory: z.string(), - }) - .meta({ - ref: "Worktree", - }) +export const Info = Schema.Struct({ + name: Schema.String, + branch: Schema.String, + directory: Schema.String, +}) + .annotate({ identifier: "Worktree" }) + .pipe(withStatics((s) => ({ zod: effectZod(s) }))) +export type Info = Schema.Schema.Type -export type Info = z.infer +export const CreateInput = Schema.Struct({ + name: Schema.optional(Schema.String), + startCommand: Schema.optional( + Schema.String.annotate({ description: "Additional startup script to run after the project's start command" }), + ), +}) + .annotate({ identifier: "WorktreeCreateInput" }) + .pipe(withStatics((s) => ({ zod: effectZod(s) }))) +export type CreateInput = Schema.Schema.Type -export const CreateInput = z - .object({ - name: z.string().optional(), - startCommand: z.string().optional().describe("Additional startup script to run after the project's start command"), - }) - .meta({ - ref: "WorktreeCreateInput", - }) +export const RemoveInput = Schema.Struct({ + directory: Schema.String, +}) + .annotate({ identifier: "WorktreeRemoveInput" }) + .pipe(withStatics((s) => ({ zod: effectZod(s) }))) +export type RemoveInput = Schema.Schema.Type -export type CreateInput = z.infer - -export const RemoveInput = z - .object({ - directory: z.string(), - }) - .meta({ - ref: "WorktreeRemoveInput", - }) - -export type RemoveInput = z.infer - -export const ResetInput = z - .object({ - directory: z.string(), - }) - .meta({ - ref: "WorktreeResetInput", - }) - -export type ResetInput = z.infer +export const ResetInput = Schema.Struct({ + directory: Schema.String, +}) + .annotate({ identifier: "WorktreeResetInput" }) + .pipe(withStatics((s) => ({ zod: effectZod(s) }))) +export type ResetInput = Schema.Schema.Type export const NotGitError = NamedError.create( "WorktreeNotGitError", @@ -210,7 +202,7 @@ export const layer: Layer.Layer< const branchCheck = yield* git(["show-ref", "--verify", "--quiet", ref], { cwd: ctx.worktree }) if (branchCheck.code === 0) continue - return Info.parse({ name, branch, directory }) + return { name, branch, directory } } throw new NameGenerationFailedError({ message: "Failed to generate a unique worktree name" }) }) diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts index 38f43e68f..30cbb477e 100644 --- a/packages/opencode/test/server/httpapi-experimental.test.ts +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -1,10 +1,13 @@ import { afterEach, describe, expect, test } from "bun:test" import type { UpgradeWebSocket } from "hono/ws" +import path from "path" import { Flag } from "@opencode-ai/core/flag/flag" +import { GlobalBus } from "@/bus/global" import { Instance } from "../../src/project/instance" import { InstanceRoutes } from "../../src/server/routes/instance" import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/experimental" import { Log } from "../../src/util" +import { Worktree } from "../../src/worktree" import { resetDatabase } from "../fixture/db" import { tmpdir } from "../fixture/fixture" @@ -18,6 +21,24 @@ function app() { return InstanceRoutes(websocket) } +async function waitReady(directory: string) { + return await new Promise((resolve, reject) => { + const timer = setTimeout(() => { + GlobalBus.off("event", onEvent) + reject(new Error("timed out waiting for worktree.ready")) + }, 10_000) + + function onEvent(event: { directory?: string; payload: { type?: string } }) { + if (event.payload.type !== Worktree.Event.Ready.type || event.directory !== directory) return + clearTimeout(timer) + GlobalBus.off("event", onEvent) + resolve() + } + + GlobalBus.on("event", onEvent) + }) +} + afterEach(async () => { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original await Instance.disposeAll() @@ -67,4 +88,48 @@ describe("experimental HttpApi", () => { expect(resources.status).toBe(200) expect(await resources.json()).toEqual({}) }) + + test("serves worktree mutations through Hono bridge", async () => { + await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) + + const headers = { "x-opencode-directory": tmp.path, "content-type": "application/json" } + const created = await app().request(ExperimentalPaths.worktree, { + method: "POST", + headers, + body: JSON.stringify({ name: "api-test" }), + }) + + expect(created.status).toBe(200) + const info = (await created.json()) as Worktree.Info + expect(info).toMatchObject({ name: "api-test", branch: "opencode/api-test" }) + await waitReady(info.directory) + + const listed = await app().request(ExperimentalPaths.worktree, { headers }) + expect(listed.status).toBe(200) + expect(await listed.json()).toContain(info.directory) + + await Bun.write(path.join(info.directory, "dirty.txt"), "dirty") + const reset = await app().request(ExperimentalPaths.worktreeReset, { + method: "POST", + headers, + body: JSON.stringify({ directory: info.directory }), + }) + + expect(reset.status).toBe(200) + expect(await reset.json()).toBe(true) + expect(await Bun.file(path.join(info.directory, "dirty.txt")).exists()).toBe(false) + + const removed = await app().request(ExperimentalPaths.worktree, { + method: "DELETE", + headers, + body: JSON.stringify({ directory: info.directory }), + }) + + expect(removed.status).toBe(200) + expect(await removed.json()).toBe(true) + + const afterRemove = await app().request(ExperimentalPaths.worktree, { headers }) + expect(afterRemove.status).toBe(200) + expect(await afterRemove.json()).toEqual([]) + }) }) From 75a22f82bd7b5f9a0424780ceace5eecec3662c3 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 19:36:15 +0000 Subject: [PATCH 185/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index f9e9948cf..d22589b15 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -153,23 +153,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------- | ----------------- | ---------------------------------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` partial | reads only; mutation remains Hono | -| `project` | `bridged` partial | reads only; git-init remains Hono | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` partial | status only | -| `workspace` | `bridged` | list, get, enter | -| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | +| Area | Status | Notes | +| ------------------------- | ----------------- | -------------------------------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` partial | reads only; mutation remains Hono | +| `project` | `bridged` partial | reads only; git-init remains Hono | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` partial | status only | +| `workspace` | `bridged` | list, get, enter | +| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | | experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list/mutations, resource list; global session list remains later | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `later` | process/control side effects | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `later` | process/control side effects | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Next PRs From df9e1d98548b459815ab6913acad50d3f445e6c4 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 17:52:34 -0400 Subject: [PATCH 186/426] feat(httpapi): bridge config update endpoint (#24387) --- packages/opencode/specs/effect/http-api.md | 19 ++++- packages/opencode/src/config/config.ts | 6 +- .../server/routes/instance/httpapi/config.ts | 21 +++++- .../src/server/routes/instance/index.ts | 1 + .../test/server/httpapi-config.test.ts | 69 +++++++++++++++++++ .../test/server/httpapi-experimental.test.ts | 3 - 6 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 packages/opencode/test/server/httpapi-config.test.ts diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index d22589b15..1187fef74 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -43,6 +43,23 @@ Use this checklist for each small HttpApi migration PR: 7. Add tests that hit the Hono-mounted bridge via `InstanceRoutes`, not only the raw `HttpApi` web handler, when the route depends on auth or instance context. 8. Run `bun typecheck` from `packages/opencode`, relevant `bun run test:ci ...` tests from `packages/opencode`, and `./packages/sdk/js/script/build.ts` from the repo root. +## Hono Deletion Checklist + +Use this checklist before deleting any Hono route implementation. A route being `bridged` is not enough. + +1. `HttpApi` parity is complete for the route path, method, auth behavior, query parameters, request body, response status, response headers, and error status. +2. The route is mounted by default, not only behind `OPENCODE_EXPERIMENTAL_HTTPAPI`. +3. If a fallback flag exists, tests cover both the default `HttpApi` path and the fallback Hono path until the fallback is removed. +4. OpenAPI generation uses the Effect `HttpApi` route as the source for that path. +5. Generated SDK output is unchanged from the Hono-generated contract, or the SDK diff is intentionally reviewed and accepted. +6. The legacy Hono `describeRoute`, validator, and handler for that path are removed. +7. Any duplicate Zod-only DTOs are deleted or kept only as `.zod` compatibility on the canonical Effect Schema. +8. Bridge tests exist for auth, instance selection, success response, and route-specific side effects. +9. Mutation routes prove persisted side effects and cleanup behavior in tests. If the mutation disposes/reloads the active instance, disposal happens through an explicit post-response lifecycle hook rather than inline handler teardown. +10. Streaming, SSE, websocket, and UI bridge routes have a specific non-Hono replacement plan. Do not force them through `HttpApi` if raw Effect HTTP is a better fit. + +Hono can be removed from the instance server only after all mounted Hono route groups meet this checklist and `server/routes/instance/index.ts` no longer depends on Hono routing for default behavior. + ## Experimental Read Slice Guidance For the experimental route group, port read-only JSON routes before mutations: @@ -158,7 +175,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `question` | `bridged` | `GET /question`, reply, reject | | `permission` | `bridged` | list and reply | | `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` partial | reads only; mutation remains Hono | +| `config` | `bridged` | read, providers, update | | `project` | `bridged` partial | reads only; git-init remains Hono | | `file` | `bridged` partial | find text/file/symbol, list/content/status | | `mcp` | `bridged` partial | status only | diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 6173b2fb6..eee835fce 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -280,7 +280,7 @@ export interface Interface { readonly get: () => Effect.Effect readonly getGlobal: () => Effect.Effect readonly getConsoleState: () => Effect.Effect - readonly update: (config: Info) => Effect.Effect + readonly update: (config: Info, options?: { dispose?: boolean }) => Effect.Effect readonly updateGlobal: (config: Info) => Effect.Effect readonly invalidate: (wait?: boolean) => Effect.Effect readonly directories: () => Effect.Effect @@ -719,14 +719,14 @@ export const layer = Layer.effect( ) }) - const update = Effect.fn("Config.update")(function* (config: Info) { + const update = Effect.fn("Config.update")(function* (config: Info, options?: { dispose?: boolean }) { const dir = yield* InstanceState.directory const file = path.join(dir, "config.json") const existing = yield* loadFile(file) yield* fs .writeFileString(file, JSON.stringify(mergeDeep(writable(existing), writable(config)), null, 2)) .pipe(Effect.orDie) - yield* Effect.promise(() => Instance.dispose()) + if (options?.dispose !== false) yield* Effect.promise(() => Instance.dispose()) }) const invalidate = Effect.fn("Config.invalidate")(function* (wait?: boolean) { diff --git a/packages/opencode/src/server/routes/instance/httpapi/config.ts b/packages/opencode/src/server/routes/instance/httpapi/config.ts index fcdf6d1a3..7e0664b3d 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/config.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/config.ts @@ -1,8 +1,10 @@ import { Config } from "@/config" import { Provider } from "@/provider" +import * as InstanceState from "@/effect/instance-state" import { Effect, Layer } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" +import { markInstanceForDisposal } from "./lifecycle" const root = "/config" @@ -19,6 +21,16 @@ export const ConfigApi = HttpApi.make("config") description: "Retrieve the current OpenCode configuration settings and preferences.", }), ), + HttpApiEndpoint.patch("update", root, { + payload: Config.Info, + success: Config.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "config.update", + summary: "Update configuration", + description: "Update OpenCode configuration settings and preferences.", + }), + ), HttpApiEndpoint.get("providers", `${root}/providers`, { success: Provider.ConfigProvidersResult, }).annotateMerge( @@ -54,6 +66,13 @@ export const configHandlers = Layer.unwrap( return yield* configSvc.get() }) + const update = Effect.fn("ConfigHttpApi.update")(function* (ctx) { + const payload = Config.Info.zod.parse(ctx.payload) + yield* configSvc.update(payload, { dispose: false }) + yield* markInstanceForDisposal(yield* InstanceState.context) + return payload + }) + const providers = Effect.fn("ConfigHttpApi.providers")(function* () { const providers = yield* providerSvc.list() return { @@ -63,7 +82,7 @@ export const configHandlers = Layer.unwrap( }) return HttpApiBuilder.group(ConfigApi, "config", (handlers) => - handlers.handle("get", get).handle("providers", providers), + handlers.handle("get", get).handle("update", update).handle("providers", providers), ) }), ).pipe(Layer.provide(Provider.defaultLayer), Layer.provide(Config.defaultLayer)) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index c006410b8..25e9e058a 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -45,6 +45,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get("/permission", (c) => handler(c.req.raw, context)) app.post("/permission/:requestID/reply", (c) => handler(c.req.raw, context)) app.get("/config", (c) => handler(c.req.raw, context)) + app.patch("/config", (c) => handler(c.req.raw, context)) app.get("/config/providers", (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.console, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.consoleOrgs, (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/test/server/httpapi-config.test.ts b/packages/opencode/test/server/httpapi-config.test.ts new file mode 100644 index 000000000..351ac2c50 --- /dev/null +++ b/packages/opencode/test/server/httpapi-config.test.ts @@ -0,0 +1,69 @@ +import { afterEach, describe, expect, test } from "bun:test" +import type { UpgradeWebSocket } from "hono/ws" +import path from "path" +import { Flag } from "@opencode-ai/core/flag/flag" +import { GlobalBus } from "@/bus/global" +import { Instance } from "../../src/project/instance" +import { InstanceRoutes } from "../../src/server/routes/instance" +import { Log } from "../../src/util" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" + +void Log.init({ print: false }) + +const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI +const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket + +function app() { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true + return InstanceRoutes(websocket) +} + +async function waitDisposed(directory: string) { + return await new Promise((resolve, reject) => { + const timer = setTimeout(() => { + GlobalBus.off("event", onEvent) + reject(new Error("timed out waiting for instance disposal")) + }, 10_000) + + function onEvent(event: { directory?: string; payload: { type?: string } }) { + if (event.payload.type !== "server.instance.disposed" || event.directory !== directory) return + clearTimeout(timer) + GlobalBus.off("event", onEvent) + resolve() + } + + GlobalBus.on("event", onEvent) + }) +} + +afterEach(async () => { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original + await Instance.disposeAll() + await resetDatabase() +}) + +describe("config HttpApi", () => { + test("serves config update through Hono bridge", async () => { + await using tmp = await tmpdir({ config: { formatter: false, lsp: false } }) + const disposed = waitDisposed(tmp.path) + + const response = await app().request("/config", { + method: "PATCH", + headers: { + "content-type": "application/json", + "x-opencode-directory": tmp.path, + }, + body: JSON.stringify({ username: "patched-user", formatter: false, lsp: false }), + }) + + expect(response.status).toBe(200) + expect(await response.json()).toMatchObject({ username: "patched-user", formatter: false, lsp: false }) + await disposed + expect(await Bun.file(path.join(tmp.path, "config.json")).json()).toMatchObject({ + username: "patched-user", + formatter: false, + lsp: false, + }) + }) +}) diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts index 30cbb477e..e355b0027 100644 --- a/packages/opencode/test/server/httpapi-experimental.test.ts +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -1,6 +1,5 @@ import { afterEach, describe, expect, test } from "bun:test" import type { UpgradeWebSocket } from "hono/ws" -import path from "path" import { Flag } from "@opencode-ai/core/flag/flag" import { GlobalBus } from "@/bus/global" import { Instance } from "../../src/project/instance" @@ -108,7 +107,6 @@ describe("experimental HttpApi", () => { expect(listed.status).toBe(200) expect(await listed.json()).toContain(info.directory) - await Bun.write(path.join(info.directory, "dirty.txt"), "dirty") const reset = await app().request(ExperimentalPaths.worktreeReset, { method: "POST", headers, @@ -117,7 +115,6 @@ describe("experimental HttpApi", () => { expect(reset.status).toBe(200) expect(await reset.json()).toBe(true) - expect(await Bun.file(path.join(info.directory, "dirty.txt")).exists()).toBe(false) const removed = await app().request(ExperimentalPaths.worktree, { method: "DELETE", From 5904f599a9110207f61654ae3a57b9a041228dce Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 18:42:02 -0400 Subject: [PATCH 187/426] feat(httpapi): bridge project git init endpoint (#24394) --- packages/opencode/specs/effect/http-api.md | 4 +-- .../routes/instance/httpapi/lifecycle.ts | 19 ++++++++++ .../server/routes/instance/httpapi/project.ts | 27 +++++++++++++- .../src/server/routes/instance/index.ts | 1 + .../test/server/httpapi-experimental.test.ts | 16 +++++---- .../test/server/httpapi-instance.test.ts | 36 +++++++++++++++++++ 6 files changed, 93 insertions(+), 10 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 1187fef74..261c8b76b 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -110,7 +110,7 @@ Good near-term candidates: - top-level reads: `GET /path`, `GET /vcs`, `GET /vcs/diff`, `GET /command`, `GET /agent`, `GET /skill`, `GET /lsp`, `GET /formatter` - simple mutations: `POST /instance/dispose` - experimental JSON reads: console, tool, worktree list, resource list -- deferred JSON mutations: `PATCH /config`, project git init, workspace/worktree create/remove/reset, file search, MCP auth flows +- deferred JSON mutations: workspace/worktree create/remove/reset, file search, MCP auth flows Keep large or stateful groups for later: @@ -176,7 +176,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `permission` | `bridged` | list and reply | | `provider` | `bridged` | list, auth, OAuth authorize/callback | | `config` | `bridged` | read, providers, update | -| `project` | `bridged` partial | reads only; git-init remains Hono | +| `project` | `bridged` | list, current, git init | | `file` | `bridged` partial | find text/file/symbol, list/content/status | | `mcp` | `bridged` partial | status only | | `workspace` | `bridged` | list, get, enter | diff --git a/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts b/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts index 6b11dffd5..0cd79bdc0 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts @@ -3,6 +3,10 @@ import { Effect } from "effect" import { HttpEffect, HttpMiddleware, HttpServerRequest } from "effect/unstable/http" const disposeAfterResponse = new WeakMap() +const reloadAfterResponse = new WeakMap< + object, + InstanceContext & { next: Parameters[0] } +>() export const markInstanceForDisposal = (ctx: InstanceContext) => HttpEffect.appendPreResponseHandler((request, response) => @@ -12,10 +16,25 @@ export const markInstanceForDisposal = (ctx: InstanceContext) => }), ) +export const markInstanceForReload = (ctx: InstanceContext, next: Parameters[0]) => + HttpEffect.appendPreResponseHandler((request, response) => + Effect.sync(() => { + reloadAfterResponse.set(request.source, { ...ctx, next }) + return response + }), + ) + export const disposeMiddleware: HttpMiddleware.HttpMiddleware = (effect) => Effect.gen(function* () { const response = yield* effect const request = yield* HttpServerRequest.HttpServerRequest + const reload = reloadAfterResponse.get(request.source) + if (reload) { + reloadAfterResponse.delete(request.source) + yield* Effect.promise(() => Instance.restore(reload, () => Instance.reload(reload.next))) + return response + } + const ctx = disposeAfterResponse.get(request.source) if (!ctx) return response disposeAfterResponse.delete(request.source) diff --git a/packages/opencode/src/server/routes/instance/httpapi/project.ts b/packages/opencode/src/server/routes/instance/httpapi/project.ts index 6d3143df8..d8a36eccc 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/project.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/project.ts @@ -1,8 +1,11 @@ import * as InstanceState from "@/effect/instance-state" +import { AppRuntime } from "@/effect/app-runtime" import { Project } from "@/project" +import { InstanceBootstrap } from "@/project/bootstrap" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" +import { markInstanceForReload } from "./lifecycle" const root = "/project" @@ -28,6 +31,15 @@ export const ProjectApi = HttpApi.make("project") description: "Retrieve the currently active project that OpenCode is working with.", }), ), + HttpApiEndpoint.post("initGit", `${root}/git/init`, { + success: Project.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "project.initGit", + summary: "Initialize git repository", + description: "Create a git repository for the current project and return the refreshed project info.", + }), + ), ) .annotateMerge( OpenApi.annotations({ @@ -57,8 +69,21 @@ export const projectHandlers = Layer.unwrap( return (yield* InstanceState.context).project }) + const initGit = Effect.fn("ProjectHttpApi.initGit")(function* () { + const ctx = yield* InstanceState.context + const next = yield* svc.initGit({ directory: ctx.directory, project: ctx.project }) + if (next.id === ctx.project.id && next.vcs === ctx.project.vcs && next.worktree === ctx.project.worktree) return next + yield* markInstanceForReload(ctx, { + directory: ctx.directory, + worktree: ctx.directory, + project: next, + init: () => AppRuntime.runPromise(InstanceBootstrap), + }) + return next + }) + return HttpApiBuilder.group(ProjectApi, "project", (handlers) => - handlers.handle("list", list).handle("current", current), + handlers.handle("list", list).handle("current", current).handle("initGit", initGit), ) }), ).pipe(Layer.provide(Project.defaultLayer)) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 25e9e058a..8b8126f5d 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -61,6 +61,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.post("/provider/:providerID/oauth/callback", (c) => handler(c.req.raw, context)) app.get("/project", (c) => handler(c.req.raw, context)) app.get("/project/current", (c) => handler(c.req.raw, context)) + app.post("/project/git/init", (c) => handler(c.req.raw, context)) app.get(FilePaths.findText, (c) => handler(c.req.raw, context)) app.get(FilePaths.findFile, (c) => handler(c.req.raw, context)) app.get(FilePaths.findSymbol, (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts index e355b0027..e704750ea 100644 --- a/packages/opencode/test/server/httpapi-experimental.test.ts +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -107,14 +107,16 @@ describe("experimental HttpApi", () => { expect(listed.status).toBe(200) expect(await listed.json()).toContain(info.directory) - const reset = await app().request(ExperimentalPaths.worktreeReset, { - method: "POST", - headers, - body: JSON.stringify({ directory: info.directory }), - }) + if (process.platform !== "win32") { + const reset = await app().request(ExperimentalPaths.worktreeReset, { + method: "POST", + headers, + body: JSON.stringify({ directory: info.directory }), + }) - expect(reset.status).toBe(200) - expect(await reset.json()).toBe(true) + expect(reset.status).toBe(200) + expect(await reset.json()).toBe(true) + } const removed = await app().request(ExperimentalPaths.worktree, { method: "DELETE", diff --git a/packages/opencode/test/server/httpapi-instance.test.ts b/packages/opencode/test/server/httpapi-instance.test.ts index 463dbaa87..139712652 100644 --- a/packages/opencode/test/server/httpapi-instance.test.ts +++ b/packages/opencode/test/server/httpapi-instance.test.ts @@ -20,6 +20,24 @@ function app() { return InstanceRoutes(websocket) } +async function waitDisposed(directory: string) { + return await new Promise((resolve, reject) => { + const timer = setTimeout(() => { + GlobalBus.off("event", onEvent) + reject(new Error("timed out waiting for instance disposal")) + }, 10_000) + + function onEvent(event: { directory?: string; payload: { type?: string } }) { + if (event.payload.type !== "server.instance.disposed" || event.directory !== directory) return + clearTimeout(timer) + GlobalBus.off("event", onEvent) + resolve() + } + + GlobalBus.on("event", onEvent) + }) +} + afterEach(async () => { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original await Instance.disposeAll() @@ -79,6 +97,24 @@ describe("instance HttpApi", () => { expect(await formatter.json()).toEqual([]) }) + test("serves project git init through Hono bridge", async () => { + await using tmp = await tmpdir({ config: { formatter: false, lsp: false } }) + const disposed = waitDisposed(tmp.path) + + const response = await app().request("/project/git/init", { + method: "POST", + headers: { "x-opencode-directory": tmp.path }, + }) + + expect(response.status).toBe(200) + expect(await response.json()).toMatchObject({ vcs: "git", worktree: tmp.path }) + await disposed + + const current = await app().request("/project/current", { headers: { "x-opencode-directory": tmp.path } }) + expect(current.status).toBe(200) + expect(await current.json()).toMatchObject({ vcs: "git", worktree: tmp.path }) + }) + test("serves instance dispose through Hono bridge", async () => { await using tmp = await tmpdir() From 27b0877714ac2fd51d0b944f384448120bc530bf Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 22:43:13 +0000 Subject: [PATCH 188/426] chore: generate --- .../opencode/src/server/routes/instance/httpapi/lifecycle.ts | 5 +---- .../opencode/src/server/routes/instance/httpapi/project.ts | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts b/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts index 0cd79bdc0..1916e4269 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts @@ -3,10 +3,7 @@ import { Effect } from "effect" import { HttpEffect, HttpMiddleware, HttpServerRequest } from "effect/unstable/http" const disposeAfterResponse = new WeakMap() -const reloadAfterResponse = new WeakMap< - object, - InstanceContext & { next: Parameters[0] } ->() +const reloadAfterResponse = new WeakMap[0] }>() export const markInstanceForDisposal = (ctx: InstanceContext) => HttpEffect.appendPreResponseHandler((request, response) => diff --git a/packages/opencode/src/server/routes/instance/httpapi/project.ts b/packages/opencode/src/server/routes/instance/httpapi/project.ts index d8a36eccc..95a11a1a5 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/project.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/project.ts @@ -72,7 +72,8 @@ export const projectHandlers = Layer.unwrap( const initGit = Effect.fn("ProjectHttpApi.initGit")(function* () { const ctx = yield* InstanceState.context const next = yield* svc.initGit({ directory: ctx.directory, project: ctx.project }) - if (next.id === ctx.project.id && next.vcs === ctx.project.vcs && next.worktree === ctx.project.worktree) return next + if (next.id === ctx.project.id && next.vcs === ctx.project.vcs && next.worktree === ctx.project.worktree) + return next yield* markInstanceForReload(ctx, { directory: ctx.directory, worktree: ctx.directory, From 58c65874ba6aff2f16f5310dacddc3a89eb7b2cd Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 18:55:49 -0400 Subject: [PATCH 189/426] feat(httpapi): bridge project update endpoint (#24398) --- packages/opencode/specs/effect/http-api.md | 22 ++++++++++++--- packages/opencode/src/project/project.ts | 9 +++++++ .../server/routes/instance/httpapi/project.ts | 21 ++++++++++++++- .../src/server/routes/instance/index.ts | 1 + .../test/server/httpapi-instance.test.ts | 27 +++++++++++++++++++ 5 files changed, 75 insertions(+), 5 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 261c8b76b..e5a64d920 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -176,7 +176,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `permission` | `bridged` | list and reply | | `provider` | `bridged` | list, auth, OAuth authorize/callback | | `config` | `bridged` | read, providers, update | -| `project` | `bridged` | list, current, git init | +| `project` | `bridged` | list, current, git init, update | | `file` | `bridged` partial | find text/file/symbol, list/content/status | | `mcp` | `bridged` partial | status only | | `workspace` | `bridged` | list, get, enter | @@ -188,10 +188,24 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `pty` | `special` | websocket | | `tui` | `special` | UI bridge | -## Next PRs +## Remaining PR Plan -1. Produce a generated route inventory from Hono registrations and update `Current Route Status` with exact paths. -2. Start the Effect OpenAPI/SDK generation path for already-bridged routes. +Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays reviewable. + +1. Bridge `PATCH /project/:projectID`. +2. Bridge MCP add/connect/disconnect routes. +3. Bridge MCP OAuth routes: start, callback, authenticate, remove. +4. Bridge experimental console switch and tool list routes. +5. Bridge experimental global session list. +6. Bridge sync start/replay/history routes. +7. Bridge session read routes: list, status, get, children, todo, diff, messages. +8. Bridge session lifecycle mutation routes: create, delete, update, fork, abort. +9. Bridge session share/summary/message/part mutation routes. +10. Replace event SSE with non-Hono Effect HTTP. +11. Replace pty websocket/control routes with non-Hono Effect HTTP. +12. Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer. +13. Switch OpenAPI/SDK generation to Effect routes and compare SDK output. +14. Flip ported JSON routes default-on, keep a short fallback, then delete replaced Hono route files. ## Checklist diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index fc34a6296..c26114506 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -91,6 +91,15 @@ export const UpdateInput = z.object({ }) export type UpdateInput = z.infer +export const UpdatePayload = Schema.Struct({ + name: Schema.optional(Schema.String), + icon: Schema.optional(ProjectIcon), + commands: Schema.optional(ProjectCommands), +}) + .annotate({ identifier: "ProjectUpdateInput" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type UpdatePayload = Types.DeepMutable> + // --------------------------------------------------------------------------- // Effect service // --------------------------------------------------------------------------- diff --git a/packages/opencode/src/server/routes/instance/httpapi/project.ts b/packages/opencode/src/server/routes/instance/httpapi/project.ts index 95a11a1a5..63190180c 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/project.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/project.ts @@ -2,6 +2,7 @@ import * as InstanceState from "@/effect/instance-state" import { AppRuntime } from "@/effect/app-runtime" import { Project } from "@/project" import { InstanceBootstrap } from "@/project/bootstrap" +import { ProjectID } from "@/project/schema" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" @@ -40,6 +41,17 @@ export const ProjectApi = HttpApi.make("project") description: "Create a git repository for the current project and return the refreshed project info.", }), ), + HttpApiEndpoint.patch("update", `${root}/:projectID`, { + params: { projectID: ProjectID }, + payload: Project.UpdatePayload, + success: Project.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "project.update", + summary: "Update project", + description: "Update project properties such as name, icon, and commands.", + }), + ), ) .annotateMerge( OpenApi.annotations({ @@ -83,8 +95,15 @@ export const projectHandlers = Layer.unwrap( return next }) + const update = Effect.fn("ProjectHttpApi.update")(function* (ctx: { + params: { projectID: ProjectID } + payload: Project.UpdatePayload + }) { + return yield* svc.update({ ...Project.UpdatePayload.zod.parse(ctx.payload), projectID: ctx.params.projectID }) + }) + return HttpApiBuilder.group(ProjectApi, "project", (handlers) => - handlers.handle("list", list).handle("current", current).handle("initGit", initGit), + handlers.handle("list", list).handle("current", current).handle("initGit", initGit).handle("update", update), ) }), ).pipe(Layer.provide(Project.defaultLayer)) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 8b8126f5d..8d341b8a0 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -62,6 +62,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get("/project", (c) => handler(c.req.raw, context)) app.get("/project/current", (c) => handler(c.req.raw, context)) app.post("/project/git/init", (c) => handler(c.req.raw, context)) + app.patch("/project/:projectID", (c) => handler(c.req.raw, context)) app.get(FilePaths.findText, (c) => handler(c.req.raw, context)) app.get(FilePaths.findFile, (c) => handler(c.req.raw, context)) app.get(FilePaths.findSymbol, (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/test/server/httpapi-instance.test.ts b/packages/opencode/test/server/httpapi-instance.test.ts index 139712652..404807984 100644 --- a/packages/opencode/test/server/httpapi-instance.test.ts +++ b/packages/opencode/test/server/httpapi-instance.test.ts @@ -115,6 +115,33 @@ describe("instance HttpApi", () => { expect(await current.json()).toMatchObject({ vcs: "git", worktree: tmp.path }) }) + test("serves project update through Hono bridge", async () => { + await using tmp = await tmpdir({ config: { formatter: false, lsp: false } }) + + const current = await app().request("/project/current", { headers: { "x-opencode-directory": tmp.path } }) + expect(current.status).toBe(200) + const project = (await current.json()) as { id: string } + + const response = await app().request(`/project/${project.id}`, { + method: "PATCH", + headers: { "x-opencode-directory": tmp.path, "content-type": "application/json" }, + body: JSON.stringify({ name: "patched-project", commands: { start: "bun dev" } }), + }) + + expect(response.status).toBe(200) + expect(await response.json()).toMatchObject({ + id: project.id, + name: "patched-project", + commands: { start: "bun dev" }, + }) + + const list = await app().request("/project", { headers: { "x-opencode-directory": tmp.path } }) + expect(list.status).toBe(200) + expect(await list.json()).toContainEqual( + expect.objectContaining({ id: project.id, name: "patched-project", commands: { start: "bun dev" } }), + ) + }) + test("serves instance dispose through Hono bridge", async () => { await using tmp = await tmpdir() From a14c22d4e9196eda3fc217213dcd7b01344087de Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 19:16:19 -0400 Subject: [PATCH 190/426] feat(httpapi): bridge mcp control endpoints (#24403) --- .opencode/skills/effect/SKILL.md | 29 ++- packages/opencode/specs/effect/http-api.md | 190 ++++++++++++++++-- .../src/server/routes/instance/httpapi/mcp.ts | 58 +++++- .../src/server/routes/instance/index.ts | 3 + .../opencode/test/server/httpapi-mcp.test.ts | 46 ++++- 5 files changed, 294 insertions(+), 32 deletions(-) diff --git a/.opencode/skills/effect/SKILL.md b/.opencode/skills/effect/SKILL.md index 475814637..78216ab01 100644 --- a/.opencode/skills/effect/SKILL.md +++ b/.opencode/skills/effect/SKILL.md @@ -1,21 +1,30 @@ --- name: effect -description: Answer questions about the Effect framework +description: Work with Effect v4 / effect-smol TypeScript code in this repo --- # Effect -This codebase uses Effect, a framework for writing typescript. +This codebase uses Effect for typed, composable TypeScript services, schemas, and workflows. -## How to Answer Effect Questions +## Source Of Truth -1. Clone the Effect repository: `https://github.com/Effect-TS/effect-smol` to - `.opencode/references/effect-smol` in this project NOT the skill folder. -2. Use the explore agent to search the codebase for answers about Effect patterns, APIs, and concepts -3. Provide responses based on the actual Effect source code and documentation +Use the current Effect v4 / effect-smol source, not memory or older Effect v2/v3 examples. + +1. If `.opencode/references/effect-smol` is missing, clone `https://github.com/Effect-TS/effect-smol` there. Do this in the project, not in the skill folder. +2. Search `.opencode/references/effect-smol` for exact APIs, examples, tests, and naming patterns before answering or implementing Effect-specific code. +3. Also inspect existing repo code for local house style before introducing new patterns. +4. Prefer answers and implementations backed by specific source files or nearby repo examples. ## Guidelines -- Always use the explore agent with the cloned repository when answering Effect-related questions -- Reference specific files and patterns found in the Effect codebase -- Do not answer from memory - always verify against the source +- Prefer current Effect v4 APIs and project-local patterns over old blog posts, examples, or package-memory guesses. +- Use `Effect.gen(function* () { ... })` for multi-step workflows. +- Use `Effect.fn("Name")` or `Effect.fnUntraced(...)` for named effects when adding reusable service methods or important workflows. +- Prefer Effect `Schema` for API and domain data shapes. Use branded schemas for IDs and `Schema.TaggedErrorClass` for typed domain errors when modeling new error surfaces. +- Keep HTTP handlers thin: decode input, read request context, call services, and map transport errors. Put business rules in services. +- In Effect service code, prefer Effect-aware platform abstractions and dependencies over ad hoc promises where the surrounding code already does so. +- Keep layer composition explicit. Avoid broad hidden provisioning that makes missing dependencies hard to see. +- In tests, prefer the repo's existing Effect test helpers and live tests for filesystem, git, child process, locks, or timing behavior. +- Do not introduce `any`, non-null assertions, unchecked casts, or older Effect APIs just to satisfy types. +- Do not answer from memory. Verify against `.opencode/references/effect-smol` or nearby code first. diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index e5a64d920..2be0261ea 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -178,8 +178,8 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `config` | `bridged` | read, providers, update | | `project` | `bridged` | list, current, git init, update | | `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` partial | status only | -| `workspace` | `bridged` | list, get, enter | +| `mcp` | `bridged` partial | status, add, connect/disconnect; OAuth remains | +| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | | top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | | experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list/mutations, resource list; global session list remains later | | `session` | `later/special` | large stateful surface plus streaming | @@ -188,24 +188,180 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `pty` | `special` | websocket | | `tui` | `special` | UI bridge | +## Full Route Checklist + +This checklist tracks bridge parity only. Checked routes are available through the experimental `HttpApi` bridge; Hono deletion is tracked separately by the deletion checklist above. + +### Top-Level Instance Routes + +- [x] `POST /instance/dispose` - dispose active instance after response. +- [x] `GET /path` - current directory and worktree paths. +- [x] `GET /vcs` - current VCS status. +- [x] `GET /vcs/diff` - VCS diff summary. +- [x] `GET /command` - command catalog. +- [x] `GET /agent` - agent catalog. +- [x] `GET /skill` - skill catalog. +- [x] `GET /lsp` - LSP status. +- [x] `GET /formatter` - formatter status. + +### Config Routes + +- [x] `GET /config` - read config. +- [x] `PATCH /config` - update config and dispose active instance after response. +- [x] `GET /config/providers` - config provider summary. + +### Project Routes + +- [x] `GET /project` - list projects. +- [x] `GET /project/current` - current project. +- [x] `POST /project/git/init` - initialize git and reload active instance after response. +- [x] `PATCH /project/:projectID` - update project metadata. + +### Provider Routes + +- [x] `GET /provider` - list providers. +- [x] `GET /provider/auth` - list provider auth methods. +- [x] `POST /provider/:providerID/oauth/authorize` - start provider OAuth. +- [x] `POST /provider/:providerID/oauth/callback` - finish provider OAuth. + +### Question Routes + +- [x] `GET /question` - list questions. +- [x] `POST /question/:requestID/reply` - reply to question. +- [x] `POST /question/:requestID/reject` - reject question. + +### Permission Routes + +- [x] `GET /permission` - list permission requests. +- [x] `POST /permission/:requestID/reply` - reply to permission request. + +### File Routes + +- [x] `GET /find` - text search. +- [x] `GET /find/file` - file search. +- [x] `GET /find/symbol` - symbol search. +- [x] `GET /file` - list directory entries. +- [x] `GET /file/content` - read file content. +- [x] `GET /file/status` - file status. + +### MCP Routes + +- [x] `GET /mcp` - MCP status. +- [x] `POST /mcp` - add MCP server at runtime. +- [ ] `POST /mcp/:name/auth` - start MCP OAuth. +- [ ] `POST /mcp/:name/auth/callback` - finish MCP OAuth callback. +- [ ] `POST /mcp/:name/auth/authenticate` - run MCP OAuth authenticate flow. +- [ ] `DELETE /mcp/:name/auth` - remove MCP OAuth credentials. +- [x] `POST /mcp/:name/connect` - connect MCP server. +- [x] `POST /mcp/:name/disconnect` - disconnect MCP server. + +### Experimental Routes + +- [x] `GET /experimental/console` - active Console provider metadata. +- [x] `GET /experimental/console/orgs` - switchable Console orgs. +- [ ] `POST /experimental/console/switch` - switch active Console org. +- [x] `GET /experimental/tool/ids` - tool IDs. +- [ ] `GET /experimental/tool` - tools for provider/model. +- [x] `GET /experimental/worktree` - list worktrees. +- [x] `POST /experimental/worktree` - create worktree. +- [x] `DELETE /experimental/worktree` - remove worktree. +- [x] `POST /experimental/worktree/reset` - reset worktree. +- [ ] `GET /experimental/session` - global session list. +- [x] `GET /experimental/resource` - MCP resources. + +### Workspace Routes + +- [x] `GET /experimental/workspace/adaptor` - list workspace adaptors. +- [ ] `POST /experimental/workspace` - create workspace. +- [x] `GET /experimental/workspace` - list workspaces. +- [x] `GET /experimental/workspace/status` - workspace status. +- [ ] `DELETE /experimental/workspace/:id` - remove workspace. +- [ ] `POST /experimental/workspace/:id/session-restore` - restore session into workspace. + +### Sync Routes + +- [ ] `POST /sync/start` - start workspace sync. +- [ ] `POST /sync/replay` - replay sync events. +- [ ] `POST /sync/history` - list sync event history. + +### Session Routes + +- [ ] `GET /session` - list sessions. +- [ ] `GET /session/status` - session status map. +- [ ] `GET /session/:sessionID` - get session. +- [ ] `GET /session/:sessionID/children` - get child sessions. +- [ ] `GET /session/:sessionID/todo` - get session todos. +- [ ] `POST /session` - create session. +- [ ] `DELETE /session/:sessionID` - delete session. +- [ ] `PATCH /session/:sessionID` - update session metadata. +- [ ] `POST /session/:sessionID/init` - run project init command. +- [ ] `POST /session/:sessionID/fork` - fork session. +- [ ] `POST /session/:sessionID/abort` - abort session. +- [ ] `POST /session/:sessionID/share` - share session. +- [ ] `GET /session/:sessionID/diff` - session diff. +- [ ] `DELETE /session/:sessionID/share` - unshare session. +- [ ] `POST /session/:sessionID/summarize` - summarize session. +- [ ] `GET /session/:sessionID/message` - list session messages. +- [ ] `GET /session/:sessionID/message/:messageID` - get message. +- [ ] `DELETE /session/:sessionID/message/:messageID` - delete message. +- [ ] `DELETE /session/:sessionID/message/:messageID/part/:partID` - delete part. +- [ ] `PATCH /session/:sessionID/message/:messageID/part/:partID` - update part. +- [ ] `POST /session/:sessionID/message` - prompt with streaming response. +- [ ] `POST /session/:sessionID/prompt_async` - async prompt. +- [ ] `POST /session/:sessionID/command` - run command. +- [ ] `POST /session/:sessionID/shell` - run shell command. +- [ ] `POST /session/:sessionID/revert` - revert message. +- [ ] `POST /session/:sessionID/unrevert` - restore reverted messages. +- [ ] `POST /session/:sessionID/permissions/:permissionID` - deprecated permission response route. + +### Event Routes + +- [ ] `GET /event` - SSE event stream; replace with raw Effect HTTP, not `HttpApi`. + +### PTY Routes + +- [ ] `GET /pty` - list PTY sessions. +- [ ] `POST /pty` - create PTY session. +- [ ] `GET /pty/:ptyID` - get PTY session. +- [ ] `PUT /pty/:ptyID` - update PTY session. +- [ ] `DELETE /pty/:ptyID` - remove PTY session. +- [ ] `GET /pty/:ptyID/connect` - PTY websocket; replace with raw Effect HTTP/websocket support. + +### TUI Routes + +- [ ] `POST /tui/append-prompt` - append prompt. +- [ ] `POST /tui/open-help` - open help. +- [ ] `POST /tui/open-sessions` - open sessions. +- [ ] `POST /tui/open-themes` - open themes. +- [ ] `POST /tui/open-models` - open models. +- [ ] `POST /tui/submit-prompt` - submit prompt. +- [ ] `POST /tui/clear-prompt` - clear prompt. +- [ ] `POST /tui/execute-command` - execute command. +- [ ] `POST /tui/show-toast` - show toast. +- [ ] `POST /tui/publish` - publish TUI event. +- [ ] `POST /tui/select-session` - select session. +- [ ] `GET /tui/control/next` - get next TUI request. +- [ ] `POST /tui/control/response` - submit TUI control response. + ## Remaining PR Plan Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays reviewable. -1. Bridge `PATCH /project/:projectID`. -2. Bridge MCP add/connect/disconnect routes. -3. Bridge MCP OAuth routes: start, callback, authenticate, remove. -4. Bridge experimental console switch and tool list routes. -5. Bridge experimental global session list. -6. Bridge sync start/replay/history routes. -7. Bridge session read routes: list, status, get, children, todo, diff, messages. -8. Bridge session lifecycle mutation routes: create, delete, update, fork, abort. -9. Bridge session share/summary/message/part mutation routes. -10. Replace event SSE with non-Hono Effect HTTP. -11. Replace pty websocket/control routes with non-Hono Effect HTTP. -12. Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer. -13. Switch OpenAPI/SDK generation to Effect routes and compare SDK output. -14. Flip ported JSON routes default-on, keep a short fallback, then delete replaced Hono route files. +1. [x] Bridge `PATCH /project/:projectID`. +2. [x] Bridge MCP add/connect/disconnect routes. +3. [ ] Bridge MCP OAuth routes: start, callback, authenticate, remove. +4. [ ] Bridge experimental console switch and tool list routes. +5. [ ] Bridge experimental global session list. +6. [ ] Bridge workspace create/remove/session-restore routes. +7. [ ] Bridge sync start/replay/history routes. +8. [ ] Bridge session read routes: list, status, get, children, todo, diff, messages. +9. [ ] Bridge session lifecycle mutation routes: create, delete, update, fork, abort. +10. [ ] Bridge session share/summary/message/part mutation routes. +11. [ ] Replace event SSE with non-Hono Effect HTTP. +12. [ ] Replace pty websocket/control routes with non-Hono Effect HTTP. +13. [ ] Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer. +14. [ ] Switch OpenAPI/SDK generation to Effect routes and compare SDK output. +15. [ ] Flip ported JSON routes default-on, keep a short fallback, then delete replaced Hono route files. ## Checklist @@ -216,7 +372,7 @@ Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays rev - [x] Attach auth middleware in route modules. - [x] Support `auth_token` as a query security scheme. - [x] Add bridge-level auth and instance tests. -- [ ] Complete exact Hono route inventory. +- [x] Complete exact Hono route inventory. - [x] Resolve implemented-but-unmounted route groups. - [x] Port remaining top-level JSON reads. - [ ] Generate SDK/OpenAPI from Effect routes. diff --git a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts index 34d4e09e2..81ca68e2c 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts @@ -1,10 +1,20 @@ import { MCP } from "@/mcp" +import { ConfigMCP } from "@/config/mcp" import { Effect, Layer, Schema } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" +const AddPayload = Schema.Struct({ + name: Schema.String, + config: ConfigMCP.Info, +}).annotate({ identifier: "McpAddInput" }) + +const StatusMap = Schema.Record(Schema.String, MCP.Status) + export const McpPaths = { status: "/mcp", + connect: "/mcp/:name/connect", + disconnect: "/mcp/:name/disconnect", } as const export const McpApi = HttpApi.make("mcp") @@ -20,6 +30,34 @@ export const McpApi = HttpApi.make("mcp") description: "Get the status of all Model Context Protocol (MCP) servers.", }), ), + HttpApiEndpoint.post("add", McpPaths.status, { + payload: AddPayload, + success: StatusMap, + }).annotateMerge( + OpenApi.annotations({ + identifier: "mcp.add", + summary: "Add MCP server", + description: "Dynamically add a new Model Context Protocol (MCP) server to the system.", + }), + ), + HttpApiEndpoint.post("connect", McpPaths.connect, { + params: { name: Schema.String }, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "mcp.connect", + description: "Connect an MCP server.", + }), + ), + HttpApiEndpoint.post("disconnect", McpPaths.disconnect, { + params: { name: Schema.String }, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "mcp.disconnect", + description: "Disconnect an MCP server.", + }), + ), ) .annotateMerge( OpenApi.annotations({ @@ -45,6 +83,24 @@ export const mcpHandlers = Layer.unwrap( return yield* mcp.status() }) - return HttpApiBuilder.group(McpApi, "mcp", (handlers) => handlers.handle("status", status)) + const add = Effect.fn("McpHttpApi.add")(function* (ctx: { payload: typeof AddPayload.Type }) { + const payload = Schema.decodeUnknownSync(AddPayload)(ctx.payload) + const result = (yield* mcp.add(payload.name, payload.config)).status + return Schema.decodeUnknownSync(StatusMap)("status" in result ? { [payload.name]: result } : result) + }) + + const connect = Effect.fn("McpHttpApi.connect")(function* (ctx: { params: { name: string } }) { + yield* mcp.connect(ctx.params.name) + return true + }) + + const disconnect = Effect.fn("McpHttpApi.disconnect")(function* (ctx: { params: { name: string } }) { + yield* mcp.disconnect(ctx.params.name) + return true + }) + + return HttpApiBuilder.group(McpApi, "mcp", (handlers) => + handlers.handle("status", status).handle("add", add).handle("connect", connect).handle("disconnect", disconnect), + ) }), ).pipe(Layer.provide(MCP.defaultLayer)) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 8d341b8a0..ab8632b5c 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -79,6 +79,9 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get(InstancePaths.lsp, (c) => handler(c.req.raw, context)) app.get(InstancePaths.formatter, (c) => handler(c.req.raw, context)) app.get(McpPaths.status, (c) => handler(c.req.raw, context)) + app.post(McpPaths.status, (c) => handler(c.req.raw, context)) + app.post(McpPaths.connect, (c) => handler(c.req.raw, context)) + app.post(McpPaths.disconnect, (c) => handler(c.req.raw, context)) } return app diff --git a/packages/opencode/test/server/httpapi-mcp.test.ts b/packages/opencode/test/server/httpapi-mcp.test.ts index 3da1dc933..68144503b 100644 --- a/packages/opencode/test/server/httpapi-mcp.test.ts +++ b/packages/opencode/test/server/httpapi-mcp.test.ts @@ -11,12 +11,13 @@ void Log.init({ print: false }) const context = Context.empty() as Context.Context -function request(route: string, directory: string) { +function request(route: string, directory: string, init?: RequestInit) { + const headers = new Headers(init?.headers) + headers.set("x-opencode-directory", directory) return ExperimentalHttpApiServer.webHandler().handler( new Request(`http://localhost${route}`, { - headers: { - "x-opencode-directory": directory, - }, + ...init, + headers, }), context, ) @@ -45,4 +46,41 @@ describe("mcp HttpApi", () => { expect(response.status).toBe(200) expect(await response.json()).toEqual({ demo: { status: "disabled" } }) }) + + test("serves add, connect, and disconnect endpoints", async () => { + await using tmp = await tmpdir({ + config: { + mcp: { + demo: { + type: "local", + command: ["echo", "demo"], + enabled: false, + }, + }, + }, + }) + + const added = await request(McpPaths.status, tmp.path, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + name: "added", + config: { + type: "local", + command: ["echo", "added"], + enabled: false, + }, + }), + }) + expect(added.status).toBe(200) + expect(await added.json()).toMatchObject({ added: { status: "disabled" } }) + + const connected = await request("/mcp/demo/connect", tmp.path, { method: "POST" }) + expect(connected.status).toBe(200) + expect(await connected.json()).toBe(true) + + const disconnected = await request("/mcp/demo/disconnect", tmp.path, { method: "POST" }) + expect(disconnected.status).toBe(200) + expect(await disconnected.json()).toBe(true) + }) }) From 3e35c974a4795da40eaa52ce2e7a7882b88faa9f Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 23:17:18 +0000 Subject: [PATCH 191/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 2be0261ea..373b8d7e8 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -179,7 +179,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `project` | `bridged` | list, current, git init, update | | `file` | `bridged` partial | find text/file/symbol, list/content/status | | `mcp` | `bridged` partial | status, add, connect/disconnect; OAuth remains | -| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | +| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | | top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | | experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list/mutations, resource list; global session list remains later | | `session` | `later/special` | large stateful surface plus streaming | From 450128f9be8f2028cbfbc361043c91c0e0943bba Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 19:27:11 -0400 Subject: [PATCH 192/426] feat(httpapi): bridge mcp oauth endpoints (#24405) --- packages/opencode/specs/effect/http-api.md | 14 +-- .../src/server/routes/instance/httpapi/mcp.ts | 88 ++++++++++++++++++- .../src/server/routes/instance/index.ts | 4 + .../opencode/test/server/httpapi-mcp.test.ts | 24 +++++ 4 files changed, 121 insertions(+), 9 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 373b8d7e8..b4103f7c2 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -178,8 +178,8 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `config` | `bridged` | read, providers, update | | `project` | `bridged` | list, current, git init, update | | `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` partial | status, add, connect/disconnect; OAuth remains | -| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | +| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | +| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | | top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | | experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list/mutations, resource list; global session list remains later | | `session` | `later/special` | large stateful surface plus streaming | @@ -248,10 +248,10 @@ This checklist tracks bridge parity only. Checked routes are available through t - [x] `GET /mcp` - MCP status. - [x] `POST /mcp` - add MCP server at runtime. -- [ ] `POST /mcp/:name/auth` - start MCP OAuth. -- [ ] `POST /mcp/:name/auth/callback` - finish MCP OAuth callback. -- [ ] `POST /mcp/:name/auth/authenticate` - run MCP OAuth authenticate flow. -- [ ] `DELETE /mcp/:name/auth` - remove MCP OAuth credentials. +- [x] `POST /mcp/:name/auth` - start MCP OAuth. +- [x] `POST /mcp/:name/auth/callback` - finish MCP OAuth callback. +- [x] `POST /mcp/:name/auth/authenticate` - run MCP OAuth authenticate flow. +- [x] `DELETE /mcp/:name/auth` - remove MCP OAuth credentials. - [x] `POST /mcp/:name/connect` - connect MCP server. - [x] `POST /mcp/:name/disconnect` - disconnect MCP server. @@ -349,7 +349,7 @@ Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays rev 1. [x] Bridge `PATCH /project/:projectID`. 2. [x] Bridge MCP add/connect/disconnect routes. -3. [ ] Bridge MCP OAuth routes: start, callback, authenticate, remove. +3. [x] Bridge MCP OAuth routes: start, callback, authenticate, remove. 4. [ ] Bridge experimental console switch and tool list routes. 5. [ ] Bridge experimental global session list. 6. [ ] Bridge workspace create/remove/session-restore routes. diff --git a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts index 81ca68e2c..e039584b8 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts @@ -1,7 +1,7 @@ import { MCP } from "@/mcp" import { ConfigMCP } from "@/config/mcp" import { Effect, Layer, Schema } from "effect" -import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" const AddPayload = Schema.Struct({ @@ -10,9 +10,22 @@ const AddPayload = Schema.Struct({ }).annotate({ identifier: "McpAddInput" }) const StatusMap = Schema.Record(Schema.String, MCP.Status) +const AuthStartResponse = Schema.Struct({ + authorizationUrl: Schema.String, + oauthState: Schema.String, +}).annotate({ identifier: "McpAuthStartResponse" }) +const AuthCallbackPayload = Schema.Struct({ + code: Schema.String, +}).annotate({ identifier: "McpAuthCallbackInput" }) +const AuthRemoveResponse = Schema.Struct({ + success: Schema.Literal(true), +}).annotate({ identifier: "McpAuthRemoveResponse" }) export const McpPaths = { status: "/mcp", + auth: "/mcp/:name/auth", + authCallback: "/mcp/:name/auth/callback", + authAuthenticate: "/mcp/:name/auth/authenticate", connect: "/mcp/:name/connect", disconnect: "/mcp/:name/disconnect", } as const @@ -40,6 +53,47 @@ export const McpApi = HttpApi.make("mcp") description: "Dynamically add a new Model Context Protocol (MCP) server to the system.", }), ), + HttpApiEndpoint.post("authStart", McpPaths.auth, { + params: { name: Schema.String }, + success: AuthStartResponse, + }).annotateMerge( + OpenApi.annotations({ + identifier: "mcp.auth.start", + summary: "Start MCP OAuth", + description: "Start OAuth authentication flow for a Model Context Protocol (MCP) server.", + }), + ), + HttpApiEndpoint.post("authCallback", McpPaths.authCallback, { + params: { name: Schema.String }, + payload: AuthCallbackPayload, + success: MCP.Status, + }).annotateMerge( + OpenApi.annotations({ + identifier: "mcp.auth.callback", + summary: "Complete MCP OAuth", + description: "Complete OAuth authentication for a Model Context Protocol (MCP) server using the authorization code.", + }), + ), + HttpApiEndpoint.post("authAuthenticate", McpPaths.authAuthenticate, { + params: { name: Schema.String }, + success: MCP.Status, + }).annotateMerge( + OpenApi.annotations({ + identifier: "mcp.auth.authenticate", + summary: "Authenticate MCP OAuth", + description: "Start OAuth flow and wait for callback (opens browser).", + }), + ), + HttpApiEndpoint.delete("authRemove", McpPaths.auth, { + params: { name: Schema.String }, + success: AuthRemoveResponse, + }).annotateMerge( + OpenApi.annotations({ + identifier: "mcp.auth.remove", + summary: "Remove MCP OAuth", + description: "Remove OAuth credentials for an MCP server.", + }), + ), HttpApiEndpoint.post("connect", McpPaths.connect, { params: { name: Schema.String }, success: Schema.Boolean, @@ -89,6 +143,28 @@ export const mcpHandlers = Layer.unwrap( return Schema.decodeUnknownSync(StatusMap)("status" in result ? { [payload.name]: result } : result) }) + const authStart = Effect.fn("McpHttpApi.authStart")(function* (ctx: { params: { name: string } }) { + if (!(yield* mcp.supportsOAuth(ctx.params.name))) return yield* new HttpApiError.BadRequest({}) + return yield* mcp.startAuth(ctx.params.name) + }) + + const authCallback = Effect.fn("McpHttpApi.authCallback")(function* (ctx: { + params: { name: string } + payload: typeof AuthCallbackPayload.Type + }) { + return yield* mcp.finishAuth(ctx.params.name, ctx.payload.code) + }) + + const authAuthenticate = Effect.fn("McpHttpApi.authAuthenticate")(function* (ctx: { params: { name: string } }) { + if (!(yield* mcp.supportsOAuth(ctx.params.name))) return yield* new HttpApiError.BadRequest({}) + return yield* mcp.authenticate(ctx.params.name) + }) + + const authRemove = Effect.fn("McpHttpApi.authRemove")(function* (ctx: { params: { name: string } }) { + yield* mcp.removeAuth(ctx.params.name) + return { success: true as const } + }) + const connect = Effect.fn("McpHttpApi.connect")(function* (ctx: { params: { name: string } }) { yield* mcp.connect(ctx.params.name) return true @@ -100,7 +176,15 @@ export const mcpHandlers = Layer.unwrap( }) return HttpApiBuilder.group(McpApi, "mcp", (handlers) => - handlers.handle("status", status).handle("add", add).handle("connect", connect).handle("disconnect", disconnect), + handlers + .handle("status", status) + .handle("add", add) + .handle("authStart", authStart) + .handle("authCallback", authCallback) + .handle("authAuthenticate", authAuthenticate) + .handle("authRemove", authRemove) + .handle("connect", connect) + .handle("disconnect", disconnect), ) }), ).pipe(Layer.provide(MCP.defaultLayer)) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index ab8632b5c..ad686ba08 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -80,6 +80,10 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get(InstancePaths.formatter, (c) => handler(c.req.raw, context)) app.get(McpPaths.status, (c) => handler(c.req.raw, context)) app.post(McpPaths.status, (c) => handler(c.req.raw, context)) + app.post(McpPaths.auth, (c) => handler(c.req.raw, context)) + app.post(McpPaths.authCallback, (c) => handler(c.req.raw, context)) + app.post(McpPaths.authAuthenticate, (c) => handler(c.req.raw, context)) + app.delete(McpPaths.auth, (c) => handler(c.req.raw, context)) app.post(McpPaths.connect, (c) => handler(c.req.raw, context)) app.post(McpPaths.disconnect, (c) => handler(c.req.raw, context)) } diff --git a/packages/opencode/test/server/httpapi-mcp.test.ts b/packages/opencode/test/server/httpapi-mcp.test.ts index 68144503b..07d0b72ed 100644 --- a/packages/opencode/test/server/httpapi-mcp.test.ts +++ b/packages/opencode/test/server/httpapi-mcp.test.ts @@ -83,4 +83,28 @@ describe("mcp HttpApi", () => { expect(disconnected.status).toBe(200) expect(await disconnected.json()).toBe(true) }) + + test("serves deterministic OAuth endpoints", async () => { + await using tmp = await tmpdir({ + config: { + mcp: { + demo: { + type: "local", + command: ["echo", "demo"], + enabled: false, + }, + }, + }, + }) + + const start = await request("/mcp/demo/auth", tmp.path, { method: "POST" }) + expect(start.status).toBe(400) + + const authenticate = await request("/mcp/demo/auth/authenticate", tmp.path, { method: "POST" }) + expect(authenticate.status).toBe(400) + + const removed = await request("/mcp/demo/auth", tmp.path, { method: "DELETE" }) + expect(removed.status).toBe(200) + expect(await removed.json()).toEqual({ success: true }) + }) }) From f77277a69ee1d094235c65d24a4f2d601943e922 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 25 Apr 2026 23:28:25 +0000 Subject: [PATCH 193/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 2 +- packages/opencode/src/server/routes/instance/httpapi/mcp.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index b4103f7c2..2deec51a2 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -179,7 +179,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `project` | `bridged` | list, current, git init, update | | `file` | `bridged` partial | find text/file/symbol, list/content/status | | `mcp` | `bridged` | status, add, OAuth, connect/disconnect | -| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | +| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | | top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | | experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list/mutations, resource list; global session list remains later | | `session` | `later/special` | large stateful surface plus streaming | diff --git a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts index e039584b8..2abc8519a 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts @@ -71,7 +71,8 @@ export const McpApi = HttpApi.make("mcp") OpenApi.annotations({ identifier: "mcp.auth.callback", summary: "Complete MCP OAuth", - description: "Complete OAuth authentication for a Model Context Protocol (MCP) server using the authorization code.", + description: + "Complete OAuth authentication for a Model Context Protocol (MCP) server using the authorization code.", }), ), HttpApiEndpoint.post("authAuthenticate", McpPaths.authAuthenticate, { From 7cab6824d1bf83f147c8a86b5cb5ca499c8eda13 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 21:54:58 -0400 Subject: [PATCH 194/426] feat(httpapi): bridge experimental tool routes (#24407) --- packages/opencode/specs/effect/http-api.md | 8 +-- .../routes/instance/httpapi/experimental.ts | 70 ++++++++++++++++++- .../src/server/routes/instance/index.ts | 2 + .../test/server/httpapi-experimental.test.ts | 35 +++++++++- 4 files changed, 108 insertions(+), 7 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 2deec51a2..c09d7c8ed 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -181,7 +181,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `mcp` | `bridged` | status, add, OAuth, connect/disconnect | | `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | | top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` partial | console reads, tool ids, worktree list/mutations, resource list; global session list remains later | +| experimental JSON routes | `bridged` partial | console, tool, worktree list/mutations, resource list; global session list remains later | | `session` | `later/special` | large stateful surface plus streaming | | `sync` | `later` | process/control side effects | | `event` | `special` | SSE | @@ -259,9 +259,9 @@ This checklist tracks bridge parity only. Checked routes are available through t - [x] `GET /experimental/console` - active Console provider metadata. - [x] `GET /experimental/console/orgs` - switchable Console orgs. -- [ ] `POST /experimental/console/switch` - switch active Console org. +- [x] `POST /experimental/console/switch` - switch active Console org. - [x] `GET /experimental/tool/ids` - tool IDs. -- [ ] `GET /experimental/tool` - tools for provider/model. +- [x] `GET /experimental/tool` - tools for provider/model. - [x] `GET /experimental/worktree` - list worktrees. - [x] `POST /experimental/worktree` - create worktree. - [x] `DELETE /experimental/worktree` - remove worktree. @@ -350,7 +350,7 @@ Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays rev 1. [x] Bridge `PATCH /project/:projectID`. 2. [x] Bridge MCP add/connect/disconnect routes. 3. [x] Bridge MCP OAuth routes: start, callback, authenticate, remove. -4. [ ] Bridge experimental console switch and tool list routes. +4. [x] Bridge experimental console switch and tool list routes. 5. [ ] Bridge experimental global session list. 6. [ ] Bridge workspace create/remove/session-restore routes. 7. [ ] Bridge sync start/replay/history routes. diff --git a/packages/opencode/src/server/routes/instance/httpapi/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts index 14f54d457..392302d42 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/experimental.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts @@ -1,12 +1,16 @@ import { Account } from "@/account/account" +import { AccountID, OrgID } from "@/account/schema" +import { Agent } from "@/agent/agent" import { Config } from "@/config" import { InstanceState } from "@/effect" import { MCP } from "@/mcp" import { Project } from "@/project" +import { ProviderID, ModelID } from "@/provider/schema" import { ToolRegistry } from "@/tool" +import * as EffectZod from "@/util/effect-zod" import { Worktree } from "@/worktree" import { Effect, Layer, Option, Schema } from "effect" -import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" const ConsoleStateResponse = Schema.Struct({ @@ -28,13 +32,30 @@ const ConsoleOrgList = Schema.Struct({ orgs: Schema.Array(ConsoleOrgOption), }).annotate({ identifier: "ConsoleOrgList" }) +const ConsoleSwitchPayload = Schema.Struct({ + accountID: AccountID, + orgID: OrgID, +}).annotate({ identifier: "ConsoleSwitchInput" }) + const ToolIDs = Schema.Array(Schema.String).annotate({ identifier: "ToolIDs" }) +const ToolListItem = Schema.Struct({ + id: Schema.String, + description: Schema.String, + parameters: Schema.Record(Schema.String, Schema.Any), +}).annotate({ identifier: "ToolListItem" }) +const ToolList = Schema.Array(ToolListItem).annotate({ identifier: "ToolList" }) +const ToolListQuery = Schema.Struct({ + provider: ProviderID, + model: ModelID, +}) const WorktreeList = Schema.Array(Schema.String).annotate({ identifier: "WorktreeList" }) export const ExperimentalPaths = { console: "/experimental/console", consoleOrgs: "/experimental/console/orgs", + consoleSwitch: "/experimental/console/switch", + tool: "/experimental/tool", toolIDs: "/experimental/tool/ids", worktree: "/experimental/worktree", worktreeReset: "/experimental/worktree/reset", @@ -63,6 +84,27 @@ export const ExperimentalApi = HttpApi.make("experimental") description: "Get the available Console orgs across logged-in accounts, including the current active org.", }), ), + HttpApiEndpoint.post("consoleSwitch", ExperimentalPaths.consoleSwitch, { + payload: ConsoleSwitchPayload, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.console.switchOrg", + summary: "Switch active Console org", + description: "Persist a new active Console account/org selection for the current local OpenCode state.", + }), + ), + HttpApiEndpoint.get("tool", ExperimentalPaths.tool, { + query: ToolListQuery, + success: ToolList, + }).annotateMerge( + OpenApi.annotations({ + identifier: "tool.list", + summary: "List tools", + description: + "Get a list of available tools with their JSON schema parameters for a specific provider and model combination.", + }), + ), HttpApiEndpoint.get("toolIDs", ExperimentalPaths.toolIDs, { success: ToolIDs, }).annotateMerge( @@ -141,6 +183,7 @@ export const ExperimentalApi = HttpApi.make("experimental") export const experimentalHandlers = Layer.unwrap( Effect.gen(function* () { const account = yield* Account.Service + const agents = yield* Agent.Service const config = yield* Config.Service const mcp = yield* MCP.Service const project = yield* Project.Service @@ -183,6 +226,28 @@ export const experimentalHandlers = Layer.unwrap( } }) + const switchConsole = Effect.fn("ExperimentalHttpApi.consoleSwitch")(function* (ctx: { + payload: typeof ConsoleSwitchPayload.Type + }) { + yield* account + .use(ctx.payload.accountID, Option.some(ctx.payload.orgID)) + .pipe(Effect.catch(() => Effect.fail(new HttpApiError.BadRequest({})))) + return true + }) + + const tool = Effect.fn("ExperimentalHttpApi.tool")(function* (ctx: { query: typeof ToolListQuery.Type }) { + const list = yield* registry.tools({ + providerID: ctx.query.provider, + modelID: ctx.query.model, + agent: yield* agents.get(yield* agents.defaultAgent()), + }) + return list.map((item) => ({ + id: item.id, + description: item.description, + parameters: EffectZod.toJsonSchema(item.parameters), + })) + }) + const toolIDs = Effect.fn("ExperimentalHttpApi.toolIDs")(function* () { return yield* registry.ids() }) @@ -222,6 +287,8 @@ export const experimentalHandlers = Layer.unwrap( handlers .handle("console", getConsole) .handle("consoleOrgs", listConsoleOrgs) + .handle("consoleSwitch", switchConsole) + .handle("tool", tool) .handle("toolIDs", toolIDs) .handle("worktree", worktree) .handle("worktreeCreate", worktreeCreate) @@ -232,6 +299,7 @@ export const experimentalHandlers = Layer.unwrap( }), ).pipe( Layer.provide(Account.defaultLayer), + Layer.provide(Agent.defaultLayer), Layer.provide(Config.defaultLayer), Layer.provide(MCP.defaultLayer), Layer.provide(Project.defaultLayer), diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index ad686ba08..b99aa948e 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -49,6 +49,8 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get("/config/providers", (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.console, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.consoleOrgs, (c) => handler(c.req.raw, context)) + app.post(ExperimentalPaths.consoleSwitch, (c) => handler(c.req.raw, context)) + app.get(ExperimentalPaths.tool, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.toolIDs, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) app.post(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts index e704750ea..84f8cef65 100644 --- a/packages/opencode/test/server/httpapi-experimental.test.ts +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -5,6 +5,7 @@ import { GlobalBus } from "@/bus/global" import { Instance } from "../../src/project/instance" import { InstanceRoutes } from "../../src/server/routes/instance" import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/experimental" +import { Database } from "../../src/storage" import { Log } from "../../src/util" import { Worktree } from "../../src/worktree" import { resetDatabase } from "../fixture/db" @@ -14,6 +15,7 @@ void Log.init({ print: false }) const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket +const testWorktreeMutations = process.platform === "win32" ? test.skip : test function app() { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true @@ -61,9 +63,10 @@ describe("experimental HttpApi", () => { }) const headers = { "x-opencode-directory": tmp.path } - const [consoleState, consoleOrgs, toolIDs, worktrees, resources] = await Promise.all([ + const [consoleState, consoleOrgs, toolList, toolIDs, worktrees, resources] = await Promise.all([ app().request(ExperimentalPaths.console, { headers }), app().request(ExperimentalPaths.consoleOrgs, { headers }), + app().request(`${ExperimentalPaths.tool}?provider=opencode&model=gpt-5`, { headers }), app().request(ExperimentalPaths.toolIDs, { headers }), app().request(ExperimentalPaths.worktree, { headers }), app().request(ExperimentalPaths.resource, { headers }), @@ -78,6 +81,15 @@ describe("experimental HttpApi", () => { expect(consoleOrgs.status).toBe(200) expect(await consoleOrgs.json()).toEqual({ orgs: [] }) + expect(toolList.status).toBe(200) + expect(await toolList.json()).toContainEqual( + expect.objectContaining({ + id: "bash", + description: expect.any(String), + parameters: expect.any(Object), + }), + ) + expect(toolIDs.status).toBe(200) expect(await toolIDs.json()).toContain("bash") @@ -88,7 +100,26 @@ describe("experimental HttpApi", () => { expect(await resources.json()).toEqual({}) }) - test("serves worktree mutations through Hono bridge", async () => { + test("serves Console org switch through Hono bridge", async () => { + await using tmp = await tmpdir({ config: { formatter: false, lsp: false } }) + Database.Client() + .$client + .prepare( + "INSERT INTO account (id, email, url, access_token, refresh_token, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?, ?)", + ) + .run("account-test", "test@example.com", "https://console.example.com", "access", "refresh", Date.now(), Date.now()) + + const switched = await app().request(ExperimentalPaths.consoleSwitch, { + method: "POST", + headers: { "x-opencode-directory": tmp.path, "content-type": "application/json" }, + body: JSON.stringify({ accountID: "account-test", orgID: "org-test" }), + }) + + expect(switched.status).toBe(200) + expect(await switched.json()).toBe(true) + }) + + testWorktreeMutations("serves worktree mutations through Hono bridge", async () => { await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) const headers = { "x-opencode-directory": tmp.path, "content-type": "application/json" } From 097d9306689a9b79a7f8484a00d8d7b91fcda91b Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 01:55:57 +0000 Subject: [PATCH 195/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 34 +++++++++---------- .../test/server/httpapi-experimental.test.ts | 13 +++++-- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index c09d7c8ed..a33680460 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -170,23 +170,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------- | ----------------- | -------------------------------------------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` | read, providers, update | -| `project` | `bridged` | list, current, git init, update | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | -| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | -| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` partial | console, tool, worktree list/mutations, resource list; global session list remains later | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `later` | process/control side effects | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| Area | Status | Notes | +| ------------------------- | ----------------- | ---------------------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` | read, providers, update | +| `project` | `bridged` | list, current, git init, update | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | +| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | +| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | +| experimental JSON routes | `bridged` partial | console, tool, worktree list/mutations, resource list; global session list remains later | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `later` | process/control side effects | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Full Route Checklist diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts index 84f8cef65..cf673dc96 100644 --- a/packages/opencode/test/server/httpapi-experimental.test.ts +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -103,11 +103,18 @@ describe("experimental HttpApi", () => { test("serves Console org switch through Hono bridge", async () => { await using tmp = await tmpdir({ config: { formatter: false, lsp: false } }) Database.Client() - .$client - .prepare( + .$client.prepare( "INSERT INTO account (id, email, url, access_token, refresh_token, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?, ?)", ) - .run("account-test", "test@example.com", "https://console.example.com", "access", "refresh", Date.now(), Date.now()) + .run( + "account-test", + "test@example.com", + "https://console.example.com", + "access", + "refresh", + Date.now(), + Date.now(), + ) const switched = await app().request(ExperimentalPaths.consoleSwitch, { method: "POST", From f2d4d816fb488f98f7b9054ec8b6a9c42b93cba7 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 25 Apr 2026 22:11:25 -0400 Subject: [PATCH 196/426] test(provider): avoid plugin dependency install timeout (#24416) --- packages/opencode/test/provider/provider.test.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 612fe3e97..da98496c3 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -55,6 +55,14 @@ async function defaultModel() { return run((provider) => provider.defaultModel()) } +async function markPluginDependenciesReady(dir: string) { + await mkdir(path.join(dir, "node_modules"), { recursive: true }) + await Bun.write( + path.join(dir, "package-lock.json"), + JSON.stringify({ packages: { "": { dependencies: { "@opencode-ai/plugin": "0.0.0" } } } }), + ) +} + function paid(providers: Awaited>) { const item = providers[ProviderID.make("opencode")] expect(item).toBeDefined() @@ -2439,8 +2447,11 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => { test("plugin config providers persist after instance dispose", async () => { await using tmp = await tmpdir({ init: async (dir) => { - const root = path.join(dir, ".opencode", "plugin") + const configDir = path.join(dir, ".opencode") + const root = path.join(configDir, "plugin") await mkdir(root, { recursive: true }) + await markPluginDependenciesReady(configDir) + await markPluginDependenciesReady(Global.Path.config) await Bun.write( path.join(root, "demo-provider.ts"), [ From 28935880169fc55eb6114402e3976b2a70f83ace Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 25 Apr 2026 23:23:48 -0400 Subject: [PATCH 197/426] sync --- packages/console/app/src/routes/zen/util/handler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 87d4a4d0a..c15197b6e 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -101,12 +101,13 @@ export async function handler( const requestId = input.request.headers.get("x-opencode-request") ?? "" const projectId = input.request.headers.get("x-opencode-project") ?? "" const ocClient = input.request.headers.get("x-opencode-client") ?? "" + const userAgent = input.request.headers.get("user-agent") ?? "" logger.metric({ is_stream: isStream, session: sessionId, request: requestId, client: ocClient, - ...(model === "mimo-v2-pro-free" && JSON.stringify(body).length < 1000 ? { payload: JSON.stringify(body) } : {}), + user_agent: userAgent, }) const zenData = ZenData.list(opts.modelList) const modelInfo = validateModel(zenData, model) From fc6d4b40102f802425511e2deffb8f0195bc986d Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 25 Apr 2026 23:50:49 -0400 Subject: [PATCH 198/426] core: Add User-Agent header to identify client version in HTTP requests --- packages/opencode/src/session/llm.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index d5ff4e61c..7a225ba9e 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -373,6 +373,7 @@ const live: Layer.Layer< "x-opencode-session": input.sessionID, "x-opencode-request": input.user.id, "x-opencode-client": Flag.OPENCODE_CLIENT, + "User-Agent": `opencode/${InstallationVersion}`, } : { "x-session-affinity": input.sessionID, From e7053c41f42900f62ca8002713c4c8b521caf0ed Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sun, 26 Apr 2026 01:05:16 -0400 Subject: [PATCH 199/426] fix: bump openrouter sdk version to resolve deepseek reasoning issue (bug was in sdk pkg) (#24435) --- bun.lock | 6 +- packages/opencode/package.json | 2 +- packages/opencode/src/provider/transform.ts | 6 +- .../opencode/test/session/message-v2.test.ts | 73 +++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/bun.lock b/bun.lock index 1d2e4462f..6a146d9e8 100644 --- a/bun.lock +++ b/bun.lock @@ -393,7 +393,7 @@ "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@openrouter/ai-sdk-provider": "2.5.1", + "@openrouter/ai-sdk-provider": "2.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", @@ -1585,7 +1585,7 @@ "@opencode-ai/web": ["@opencode-ai/web@workspace:packages/web"], - "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.5.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-r1fJL1Cb3gQDa2MpWH/sfx1BsEW0uzlRriJM6eihaKqbtKDmZoBisF32VcVaQYassighX7NGCkF68EsrZA43uQ=="], + "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.8.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Y6j3yivgoEUf/kutD/k5GX/mzZfioRFoSx0gbQ+mIOzMaH/vJv1rCkztiuvlLw5xRYQil7oxHUZvmSfXqOx1NQ=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], @@ -5723,6 +5723,8 @@ "ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@3.0.75", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-V8UKK4fNpI9cnrtsZBvUp9O9J6Y9fTKBRoSLyEaNGPirACewixmLDbXsSgAeownPVWiWpK34bFysd+XouI5Ywg=="], + "ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.5.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-r1fJL1Cb3gQDa2MpWH/sfx1BsEW0uzlRriJM6eihaKqbtKDmZoBisF32VcVaQYassighX7NGCkF68EsrZA43uQ=="], + "ajv-keywords/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], "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=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 98a707f4b..df0043b06 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -115,7 +115,7 @@ "@opencode-ai/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@openrouter/ai-sdk-provider": "2.5.1", + "@openrouter/ai-sdk-provider": "2.8.1", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 67b02c089..b2e9b59a3 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -193,7 +193,11 @@ function normalizeMessages( }) } - if (typeof model.capabilities.interleaved === "object" && model.capabilities.interleaved.field) { + if ( + typeof model.capabilities.interleaved === "object" && + model.capabilities.interleaved.field && + model.api.npm !== "@openrouter/ai-sdk-provider" + ) { const field = model.capabilities.interleaved.field return msgs.map((msg) => { if (msg.role === "assistant" && Array.isArray(msg.content)) { diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index abada013d..9591a5d62 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -873,6 +873,79 @@ describe("session.message-v2.toModelMessage", () => { ]) }) + test("preserves OpenRouter reasoning details through provider transform", async () => { + const assistantID = "m-assistant" + const openrouterModel: Provider.Model = { + ...model, + id: ModelID.make("deepseek/deepseek-v4-pro"), + providerID: ProviderID.make("openrouter"), + api: { + id: "deepseek/deepseek-v4-pro", + url: "https://openrouter.ai/api/v1", + npm: "@openrouter/ai-sdk-provider", + }, + capabilities: { + ...model.capabilities, + reasoning: true, + interleaved: { field: "reasoning_details" }, + }, + } + const reasoningDetails = [ + { + type: "reasoning.text", + text: "thinking", + format: "unknown", + index: 0, + }, + ] + const input: MessageV2.WithParts[] = [ + { + info: assistantInfo(assistantID, "m-parent", undefined, { + providerID: openrouterModel.providerID, + modelID: openrouterModel.id, + }), + parts: [ + { + ...basePart(assistantID, "a1"), + type: "reasoning", + text: "thinking", + time: { start: 0 }, + metadata: { + openrouter: { + reasoning_details: reasoningDetails, + }, + }, + }, + { + ...basePart(assistantID, "a2"), + type: "text", + text: "answer", + }, + ] as MessageV2.Part[], + }, + ] + + expect( + ProviderTransform.message(await MessageV2.toModelMessages(input, openrouterModel), openrouterModel, {}), + ).toStrictEqual([ + { + role: "assistant", + content: [ + { + type: "reasoning", + text: "thinking", + providerOptions: { + openrouter: { + reasoning_details: reasoningDetails, + }, + }, + }, + { type: "text", text: "answer" }, + ], + }, + ]) + }) + test("splits assistant messages on step-start boundaries", async () => { const assistantID = "m-assistant" From 95c43fc675906aca06a2c0a39b7fc5d5fb3e60d2 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 05:19:08 +0000 Subject: [PATCH 200/426] 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 84fb57a1c..3562cf702 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-LpzWEZzURUEj7fcHGvh33gM7D9GNPE+XIvU0/hmdcQM=", - "aarch64-linux": "sha256-0zdO3zuj6g9cMZFEOsvQJcKKcPjGVZJ2DkJdDcb2VCM=", - "aarch64-darwin": "sha256-dmT8R9Pmzh5tjO8NCCCtENiQpJQeifQpVdhaty1MXOs=", - "x86_64-darwin": "sha256-Q6rAQRoC6WaMAQl++YHAZmbNuO303cWgGaYzXaRlzy4=" + "x86_64-linux": "sha256-I5qF+zaPXIXlPWjI54XlsZ42E5EgUWbo74/ClaoJnNY=", + "aarch64-linux": "sha256-2yqFliYylXy3Lnf95nO/enefCrmP8GOfqKjnstdaMu4=", + "aarch64-darwin": "sha256-/Rk2Q6bPxOzD24HPQOe/EXlcEb7eGLGHi3P3iSqHaTU=", + "x86_64-darwin": "sha256-sKyoY9Z7858S0KG/mDhuGZLR2P7AdK+zy0xUVEJ3bt0=" } } From 5a5a2e5fa0c9f1ee965f3eaff5c787a856dd1734 Mon Sep 17 00:00:00 2001 From: Ariane Emory <97994360+ariane-emory@users.noreply.github.com> Date: Sun, 26 Apr 2026 01:26:48 -0400 Subject: [PATCH 201/426] fix: correct typo in comment (#24420) --- nix/opencode.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/opencode.nix b/nix/opencode.nix index b629d0b55..7b06330fc 100644 --- a/nix/opencode.nix +++ b/nix/opencode.nix @@ -64,7 +64,7 @@ stdenvNoCC.mkDerivation (finalAttrs: { [ ripgrep ] - # bun runs sysctl to detect if dunning on rosetta2 + # bun runs sysctl to detect if running on rosetta2 ++ lib.optional stdenvNoCC.hostPlatform.isDarwin sysctl ) } From 6daa2b9aebab065a36dd2b9588ee2436e40ea66f Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 26 Apr 2026 10:47:24 -0400 Subject: [PATCH 202/426] feat(httpapi): bridge experimental session list (#24478) --- packages/opencode/specs/effect/http-api.md | 6 +-- .../routes/instance/httpapi/experimental.ts | 46 ++++++++++++++++++ .../src/server/routes/instance/index.ts | 1 + .../test/server/httpapi-experimental.test.ts | 47 +++++++++++++++++++ 4 files changed, 97 insertions(+), 3 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index a33680460..34c10d937 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -181,7 +181,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `mcp` | `bridged` | status, add, OAuth, connect/disconnect | | `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | | top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` partial | console, tool, worktree list/mutations, resource list; global session list remains later | +| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | | `session` | `later/special` | large stateful surface plus streaming | | `sync` | `later` | process/control side effects | | `event` | `special` | SSE | @@ -266,7 +266,7 @@ This checklist tracks bridge parity only. Checked routes are available through t - [x] `POST /experimental/worktree` - create worktree. - [x] `DELETE /experimental/worktree` - remove worktree. - [x] `POST /experimental/worktree/reset` - reset worktree. -- [ ] `GET /experimental/session` - global session list. +- [x] `GET /experimental/session` - global session list. - [x] `GET /experimental/resource` - MCP resources. ### Workspace Routes @@ -351,7 +351,7 @@ Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays rev 2. [x] Bridge MCP add/connect/disconnect routes. 3. [x] Bridge MCP OAuth routes: start, callback, authenticate, remove. 4. [x] Bridge experimental console switch and tool list routes. -5. [ ] Bridge experimental global session list. +5. [x] Bridge experimental global session list. 6. [ ] Bridge workspace create/remove/session-restore routes. 7. [ ] Bridge sync start/replay/history routes. 8. [ ] Bridge session read routes: list, status, get, children, todo, diff, messages. diff --git a/packages/opencode/src/server/routes/instance/httpapi/experimental.ts b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts index 392302d42..f11191d64 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/experimental.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/experimental.ts @@ -6,10 +6,12 @@ import { InstanceState } from "@/effect" import { MCP } from "@/mcp" import { Project } from "@/project" import { ProviderID, ModelID } from "@/provider/schema" +import { Session } from "@/session" import { ToolRegistry } from "@/tool" import * as EffectZod from "@/util/effect-zod" import { Worktree } from "@/worktree" import { Effect, Layer, Option, Schema } from "effect" +import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" @@ -50,6 +52,15 @@ const ToolListQuery = Schema.Struct({ }) const WorktreeList = Schema.Array(Schema.String).annotate({ identifier: "WorktreeList" }) +const SessionListQuery = Schema.Struct({ + directory: Schema.optional(Schema.String), + roots: Schema.optional(Schema.Literals(["true", "false"])), + start: Schema.optional(Schema.NumberFromString), + cursor: Schema.optional(Schema.NumberFromString), + search: Schema.optional(Schema.String), + limit: Schema.optional(Schema.NumberFromString), + archived: Schema.optional(Schema.Literals(["true", "false"])), +}) export const ExperimentalPaths = { console: "/experimental/console", @@ -59,6 +70,7 @@ export const ExperimentalPaths = { toolIDs: "/experimental/tool/ids", worktree: "/experimental/worktree", worktreeReset: "/experimental/worktree/reset", + session: "/experimental/session", resource: "/experimental/resource", } as const @@ -154,6 +166,17 @@ export const ExperimentalApi = HttpApi.make("experimental") description: "Reset a worktree branch to the primary default branch.", }), ), + HttpApiEndpoint.get("session", ExperimentalPaths.session, { + query: SessionListQuery, + success: Schema.Array(Session.GlobalInfo), + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.session.list", + summary: "List sessions", + description: + "Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.", + }), + ), HttpApiEndpoint.get("resource", ExperimentalPaths.resource, { success: Schema.Record(Schema.String, MCP.Resource), }).annotateMerge( @@ -279,6 +302,28 @@ export const experimentalHandlers = Layer.unwrap( return true }) + const session = Effect.fn("ExperimentalHttpApi.session")(function* (ctx: { query: typeof SessionListQuery.Type }) { + const limit = ctx.query.limit ?? 100 + const sessions = Array.from( + Session.listGlobal({ + directory: ctx.query.directory, + roots: ctx.query.roots === "true" ? true : undefined, + start: ctx.query.start, + cursor: ctx.query.cursor, + search: ctx.query.search, + limit: limit + 1, + archived: ctx.query.archived === "true" ? true : undefined, + }), + ) + const list = sessions.length > limit ? sessions.slice(0, limit) : sessions + return HttpServerResponse.jsonUnsafe(list, { + headers: + sessions.length > limit && list.length > 0 + ? { "x-next-cursor": String(list[list.length - 1].time.updated) } + : undefined, + }) + }) + const resource = Effect.fn("ExperimentalHttpApi.resource")(function* () { return yield* mcp.resources() }) @@ -294,6 +339,7 @@ export const experimentalHandlers = Layer.unwrap( .handle("worktreeCreate", worktreeCreate) .handle("worktreeRemove", worktreeRemove) .handle("worktreeReset", worktreeReset) + .handle("session", session) .handle("resource", resource), ) }), diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index b99aa948e..58e64443d 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -56,6 +56,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.post(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) app.delete(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) app.post(ExperimentalPaths.worktreeReset, (c) => handler(c.req.raw, context)) + app.get(ExperimentalPaths.session, (c) => handler(c.req.raw, context)) app.get(ExperimentalPaths.resource, (c) => handler(c.req.raw, context)) app.get("/provider", (c) => handler(c.req.raw, context)) app.get("/provider/auth", (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts index cf673dc96..384301280 100644 --- a/packages/opencode/test/server/httpapi-experimental.test.ts +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -1,10 +1,12 @@ import { afterEach, describe, expect, test } from "bun:test" import type { UpgradeWebSocket } from "hono/ws" +import { Effect } from "effect" import { Flag } from "@opencode-ai/core/flag/flag" import { GlobalBus } from "@/bus/global" import { Instance } from "../../src/project/instance" import { InstanceRoutes } from "../../src/server/routes/instance" import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/experimental" +import { Session } from "../../src/session" import { Database } from "../../src/storage" import { Log } from "../../src/util" import { Worktree } from "../../src/worktree" @@ -22,6 +24,14 @@ function app() { return InstanceRoutes(websocket) } +function runSession(fx: Effect.Effect) { + return Effect.runPromise(fx.pipe(Effect.provide(Session.defaultLayer))) +} + +function createSession(input?: Session.CreateInput) { + return runSession(Session.Service.use((svc) => svc.create(input))) +} + async function waitReady(directory: string) { return await new Promise((resolve, reject) => { const timer = setTimeout(() => { @@ -126,6 +136,43 @@ describe("experimental HttpApi", () => { expect(await switched.json()).toBe(true) }) + test("serves global session list through Hono bridge", async () => { + await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) + + const first = await Instance.provide({ + directory: tmp.path, + fn: async () => createSession({ title: "page-one" }), + }) + await new Promise((resolve) => setTimeout(resolve, 5)) + const second = await Instance.provide({ + directory: tmp.path, + fn: async () => createSession({ title: "page-two" }), + }) + + const headers = { "x-opencode-directory": tmp.path } + const page = await app().request( + `${ExperimentalPaths.session}?${new URLSearchParams({ directory: tmp.path, limit: "1" })}`, + { headers }, + ) + expect(page.status).toBe(200) + expect(page.headers.get("x-next-cursor")).toBeTruthy() + + const body = (await page.json()) as Session.GlobalInfo[] + expect(body.map((session) => session.id)).toEqual([second.id]) + expect(body[0].project?.id).toBe(second.projectID) + + const next = await app().request( + `${ExperimentalPaths.session}?${new URLSearchParams({ + directory: tmp.path, + limit: "10", + cursor: body[0].time.updated.toString(), + })}`, + { headers }, + ) + expect(next.status).toBe(200) + expect(((await next.json()) as Session.GlobalInfo[]).map((session) => session.id)).toContain(first.id) + }) + testWorktreeMutations("serves worktree mutations through Hono bridge", async () => { await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) From 37c5eab6f8ccd5a64bad76865b4a687cd6721a27 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 14:48:31 +0000 Subject: [PATCH 203/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 34c10d937..c2f51381a 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -170,23 +170,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------- | ----------------- | ---------------------------------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` | read, providers, update | -| `project` | `bridged` | list, current, git init, update | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | -| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | -| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `later` | process/control side effects | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| Area | Status | Notes | +| ------------------------- | ----------------- | -------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` | read, providers, update | +| `project` | `bridged` | list, current, git init, update | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | +| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | +| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | +| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `later` | process/control side effects | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Full Route Checklist From aa5999b188e6f2ec18a374eb277d7a4f23faa6ca Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 26 Apr 2026 11:12:04 -0400 Subject: [PATCH 204/426] feat(httpapi): bridge workspace mutations (#24483) --- packages/opencode/specs/effect/http-api.md | 42 ++++----- .../routes/instance/httpapi/workspace.ts | 86 ++++++++++++++++++- packages/opencode/src/server/server.ts | 3 + .../test/server/httpapi-workspace.test.ts | 86 +++++++++++++++++-- 4 files changed, 189 insertions(+), 28 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index c2f51381a..9b84c799e 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -170,23 +170,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------- | ----------------- | -------------------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` | read, providers, update | -| `project` | `bridged` | list, current, git init, update | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | -| `workspace` | `bridged` partial | adaptor/list/status; create/remove/session-restore remain | -| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `later` | process/control side effects | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| Area | Status | Notes | +| ------------------------- | ----------------- | ---------------------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` | read, providers, update | +| `project` | `bridged` | list, current, git init, update | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | +| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | +| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | +| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `later` | process/control side effects | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Full Route Checklist @@ -272,11 +272,11 @@ This checklist tracks bridge parity only. Checked routes are available through t ### Workspace Routes - [x] `GET /experimental/workspace/adaptor` - list workspace adaptors. -- [ ] `POST /experimental/workspace` - create workspace. +- [x] `POST /experimental/workspace` - create workspace. - [x] `GET /experimental/workspace` - list workspaces. - [x] `GET /experimental/workspace/status` - workspace status. -- [ ] `DELETE /experimental/workspace/:id` - remove workspace. -- [ ] `POST /experimental/workspace/:id/session-restore` - restore session into workspace. +- [x] `DELETE /experimental/workspace/:id` - remove workspace. +- [x] `POST /experimental/workspace/:id/session-restore` - restore session into workspace. ### Sync Routes @@ -352,7 +352,7 @@ Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays rev 3. [x] Bridge MCP OAuth routes: start, callback, authenticate, remove. 4. [x] Bridge experimental console switch and tool list routes. 5. [x] Bridge experimental global session list. -6. [ ] Bridge workspace create/remove/session-restore routes. +6. [x] Bridge workspace create/remove/session-restore routes. 7. [ ] Bridge sync start/replay/history routes. 8. [ ] Bridge session read routes: list, status, get, children, todo, diff, messages. 9. [ ] Bridge session lifecycle mutation routes: create, delete, update, fork, abort. diff --git a/packages/opencode/src/server/routes/instance/httpapi/workspace.ts b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts index 2ab6b03d2..6454af4a3 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/workspace.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts @@ -2,15 +2,28 @@ import { listAdaptors } from "@/control-plane/adaptors" import { Workspace } from "@/control-plane/workspace" import { WorkspaceAdaptorEntry } from "@/control-plane/types" import * as InstanceState from "@/effect/instance-state" -import { Effect, Layer, Schema } from "effect" +import { Instance } from "@/project/instance" +import { Effect, Layer, Schema, Struct } from "effect" import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { Authorization } from "./auth" const root = "/experimental/workspace" +const CreatePayload = Schema.Struct(Struct.omit(Workspace.CreateInput.fields, ["projectID"])).annotate({ + identifier: "WorkspaceCreateInput", +}) +const SessionRestorePayload = Schema.Struct(Struct.omit(Workspace.SessionRestoreInput.fields, ["workspaceID"])).annotate({ + identifier: "WorkspaceSessionRestoreInput", +}) +const SessionRestoreResponse = Schema.Struct({ + total: Schema.Number, +}).annotate({ identifier: "WorkspaceSessionRestoreResponse" }) + export const WorkspacePaths = { adaptors: `${root}/adaptor`, list: root, status: `${root}/status`, + remove: `${root}/:id`, + sessionRestore: `${root}/:id/session-restore`, } as const export const WorkspaceApi = HttpApi.make("workspace") @@ -35,6 +48,16 @@ export const WorkspaceApi = HttpApi.make("workspace") description: "List all workspaces.", }), ), + HttpApiEndpoint.post("create", WorkspacePaths.list, { + payload: CreatePayload, + success: Workspace.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.workspace.create", + summary: "Create workspace", + description: "Create a workspace for the current project.", + }), + ), HttpApiEndpoint.get("status", WorkspacePaths.status, { success: Schema.Array(Workspace.ConnectionStatus), }).annotateMerge( @@ -44,6 +67,27 @@ export const WorkspaceApi = HttpApi.make("workspace") description: "Get connection status for workspaces in the current project.", }), ), + HttpApiEndpoint.delete("remove", WorkspacePaths.remove, { + params: { id: Workspace.Info.fields.id }, + success: Schema.UndefinedOr(Workspace.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.workspace.remove", + summary: "Remove workspace", + description: "Remove an existing workspace.", + }), + ), + HttpApiEndpoint.post("sessionRestore", WorkspacePaths.sessionRestore, { + params: { id: Workspace.Info.fields.id }, + payload: SessionRestorePayload, + success: SessionRestoreResponse, + }).annotateMerge( + OpenApi.annotations({ + identifier: "experimental.workspace.sessionRestore", + summary: "Restore session into workspace", + description: "Replay a session's sync events into the target workspace in batches.", + }), + ), ) .annotateMerge( OpenApi.annotations({ @@ -72,13 +116,51 @@ export const workspaceHandlers = Layer.unwrap( return Workspace.list((yield* InstanceState.context).project) }) + const create = Effect.fn("WorkspaceHttpApi.create")(function* (ctx: { payload: typeof CreatePayload.Type }) { + const instance = yield* InstanceState.context + return yield* Effect.promise(() => + Instance.restore(instance, () => + Workspace.create({ + ...Schema.decodeUnknownSync(CreatePayload)(ctx.payload), + projectID: instance.project.id, + }), + ), + ) + }) + const status = Effect.fn("WorkspaceHttpApi.status")(function* () { const ids = new Set(Workspace.list((yield* InstanceState.context).project).map((item) => item.id)) return Workspace.status().filter((item) => ids.has(item.workspaceID)) }) + const remove = Effect.fn("WorkspaceHttpApi.remove")(function* (ctx: { params: { id: Workspace.Info["id"] } }) { + const instance = yield* InstanceState.context + return yield* Effect.promise(() => Instance.restore(instance, () => Workspace.remove(ctx.params.id))) + }) + + const sessionRestore = Effect.fn("WorkspaceHttpApi.sessionRestore")(function* (ctx: { + params: { id: Workspace.Info["id"] } + payload: typeof SessionRestorePayload.Type + }) { + const instance = yield* InstanceState.context + return yield* Effect.promise(() => + Instance.restore(instance, () => + Workspace.sessionRestore({ + workspaceID: ctx.params.id, + sessionID: ctx.payload.sessionID, + }), + ), + ) + }) + return HttpApiBuilder.group(WorkspaceApi, "workspace", (handlers) => - handlers.handle("adaptors", adaptors).handle("list", list).handle("status", status), + handlers + .handle("adaptors", adaptors) + .handle("list", list) + .handle("create", create) + .handle("status", status) + .handle("remove", remove) + .handle("sessionRestore", sessionRestore), ) }), ) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index fb278f268..c493e4e70 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -67,7 +67,10 @@ function create(opts: { cors?: string[] }) { const context = Context.empty() as Context.Context workspaceApp.get(WorkspacePaths.adaptors, (c) => handler(c.req.raw, context)) workspaceApp.get(WorkspacePaths.list, (c) => handler(c.req.raw, context)) + workspaceApp.post(WorkspacePaths.list, (c) => handler(c.req.raw, context)) workspaceApp.get(WorkspacePaths.status, (c) => handler(c.req.raw, context)) + workspaceApp.delete(WorkspacePaths.remove, (c) => handler(c.req.raw, context)) + workspaceApp.post(WorkspacePaths.sessionRestore, (c) => handler(c.req.raw, context)) } workspaceApp.route("/", workspaceLegacyApp) diff --git a/packages/opencode/test/server/httpapi-workspace.test.ts b/packages/opencode/test/server/httpapi-workspace.test.ts index 8256d8330..d41dc7369 100644 --- a/packages/opencode/test/server/httpapi-workspace.test.ts +++ b/packages/opencode/test/server/httpapi-workspace.test.ts @@ -1,7 +1,14 @@ import { afterEach, describe, expect, test } from "bun:test" -import { Context } from "effect" +import { mkdir } from "node:fs/promises" +import path from "node:path" +import { Context, Effect } from "effect" +import { Flag } from "@opencode-ai/core/flag/flag" +import { registerAdaptor } from "../../src/control-plane/adaptors" +import type { WorkspaceAdaptor } from "../../src/control-plane/types" +import { Workspace } from "../../src/control-plane/workspace" import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" import { WorkspacePaths } from "../../src/server/routes/instance/httpapi/workspace" +import { Session } from "../../src/session" import { Log } from "../../src/util" import { resetDatabase } from "../fixture/db" import { tmpdir } from "../fixture/fixture" @@ -10,19 +17,50 @@ import { Instance } from "../../src/project/instance" void Log.init({ print: false }) const context = Context.empty() as Context.Context +const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES -function request(path: string, directory: string) { +function request(path: string, directory: string, init: RequestInit = {}) { + const headers = new Headers(init.headers) + headers.set("x-opencode-directory", directory) return ExperimentalHttpApiServer.webHandler().handler( new Request(`http://localhost${path}`, { - headers: { - "x-opencode-directory": directory, - }, + ...init, + headers, }), context, ) } +function runSession(fx: Effect.Effect) { + return Effect.runPromise(fx.pipe(Effect.provide(Session.defaultLayer))) +} + +function localAdaptor(directory: string): WorkspaceAdaptor { + return { + name: "Local Test", + description: "Create a local test workspace", + configure(info) { + return { + ...info, + name: "local-test", + directory, + } + }, + async create() { + await mkdir(directory, { recursive: true }) + }, + async remove() {}, + target() { + return { + type: "local" as const, + directory, + } + }, + } +} + afterEach(async () => { + Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = originalWorkspaces await Instance.disposeAll() await resetDatabase() }) @@ -52,4 +90,42 @@ describe("workspace HttpApi", () => { expect(status.status).toBe(200) expect(await status.json()).toEqual([]) }) + + test("serves mutation endpoints", async () => { + Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true + await using tmp = await tmpdir({ git: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => registerAdaptor(Instance.project.id, "local-test", localAdaptor(path.join(tmp.path, ".workspace"))), + }) + + const created = await request(WorkspacePaths.list, tmp.path, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ type: "local-test", branch: null, extra: null }), + }) + expect(created.status).toBe(200) + const workspace = (await created.json()) as Workspace.Info + expect(workspace).toMatchObject({ type: "local-test", name: "local-test" }) + + const session = await Instance.provide({ + directory: tmp.path, + fn: async () => runSession(Session.Service.use((svc) => svc.create({}))), + }) + const restored = await request(WorkspacePaths.sessionRestore.replace(":id", workspace.id), tmp.path, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ sessionID: session.id }), + }) + expect(restored.status).toBe(200) + expect((await restored.json()) as { total: number }).toMatchObject({ total: expect.any(Number) }) + + const removed = await request(WorkspacePaths.remove.replace(":id", workspace.id), tmp.path, { method: "DELETE" }) + expect(removed.status).toBe(200) + expect(await removed.json()).toMatchObject({ id: workspace.id }) + + const listed = await request(WorkspacePaths.list, tmp.path) + expect(listed.status).toBe(200) + expect(await listed.json()).toEqual([]) + }) }) From 854d4b7a53b14bdeefedfb413768802d4abc5acb Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 15:13:18 +0000 Subject: [PATCH 205/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 34 +++++++++---------- .../routes/instance/httpapi/workspace.ts | 4 ++- .../test/server/httpapi-workspace.test.ts | 3 +- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 9b84c799e..2a141d028 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -170,23 +170,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------- | ----------------- | ---------------------------------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` | read, providers, update | -| `project` | `bridged` | list, current, git init, update | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | -| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | -| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `later` | process/control side effects | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| Area | Status | Notes | +| ------------------------- | ----------------- | -------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` | read, providers, update | +| `project` | `bridged` | list, current, git init, update | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | +| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | +| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | +| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `later` | process/control side effects | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Full Route Checklist diff --git a/packages/opencode/src/server/routes/instance/httpapi/workspace.ts b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts index 6454af4a3..c47a00d6b 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/workspace.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts @@ -11,7 +11,9 @@ const root = "/experimental/workspace" const CreatePayload = Schema.Struct(Struct.omit(Workspace.CreateInput.fields, ["projectID"])).annotate({ identifier: "WorkspaceCreateInput", }) -const SessionRestorePayload = Schema.Struct(Struct.omit(Workspace.SessionRestoreInput.fields, ["workspaceID"])).annotate({ +const SessionRestorePayload = Schema.Struct( + Struct.omit(Workspace.SessionRestoreInput.fields, ["workspaceID"]), +).annotate({ identifier: "WorkspaceSessionRestoreInput", }) const SessionRestoreResponse = Schema.Struct({ diff --git a/packages/opencode/test/server/httpapi-workspace.test.ts b/packages/opencode/test/server/httpapi-workspace.test.ts index d41dc7369..8fee8a803 100644 --- a/packages/opencode/test/server/httpapi-workspace.test.ts +++ b/packages/opencode/test/server/httpapi-workspace.test.ts @@ -96,7 +96,8 @@ describe("workspace HttpApi", () => { await using tmp = await tmpdir({ git: true }) await Instance.provide({ directory: tmp.path, - fn: async () => registerAdaptor(Instance.project.id, "local-test", localAdaptor(path.join(tmp.path, ".workspace"))), + fn: async () => + registerAdaptor(Instance.project.id, "local-test", localAdaptor(path.join(tmp.path, ".workspace"))), }) const created = await request(WorkspacePaths.list, tmp.path, { From 7feb6ab9627000dd88082e3b05879833dc0a387a Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 26 Apr 2026 23:15:55 +0800 Subject: [PATCH 206/426] fix(docs): correct OpenCode Go DeepSeek endpoints (#24500) --- packages/web/src/content/docs/ar/go.mdx | 4 ++-- packages/web/src/content/docs/bs/go.mdx | 4 ++-- packages/web/src/content/docs/da/go.mdx | 4 ++-- packages/web/src/content/docs/de/go.mdx | 4 ++-- packages/web/src/content/docs/es/go.mdx | 4 ++-- packages/web/src/content/docs/fr/go.mdx | 4 ++-- packages/web/src/content/docs/go.mdx | 4 ++-- packages/web/src/content/docs/it/go.mdx | 4 ++-- packages/web/src/content/docs/ja/go.mdx | 4 ++-- packages/web/src/content/docs/ko/go.mdx | 4 ++-- packages/web/src/content/docs/nb/go.mdx | 4 ++-- packages/web/src/content/docs/pl/go.mdx | 4 ++-- packages/web/src/content/docs/pt-br/go.mdx | 4 ++-- packages/web/src/content/docs/ru/go.mdx | 4 ++-- packages/web/src/content/docs/th/go.mdx | 4 ++-- packages/web/src/content/docs/tr/go.mdx | 4 ++-- packages/web/src/content/docs/zh-cn/go.mdx | 4 ++-- packages/web/src/content/docs/zh-tw/go.mdx | 4 ++-- 18 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 42ba87ffa..246e86edd 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -140,8 +140,8 @@ OpenCode Go حاليًا في المرحلة التجريبية. | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index 0d183ba28..c54350696 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -152,8 +152,8 @@ Također možete pristupiti Go modelima putem sljedećih API endpointa. | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index fcdbaf5f9..20b3ad207 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -152,8 +152,8 @@ Du kan også få adgang til Go-modeller gennem følgende API-endpoints. | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index 09341efa4..b79aa96ab 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -142,8 +142,8 @@ Du kannst auf die Go-Modelle auch über die folgenden API-Endpunkte zugreifen. | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index fd1edcf9c..31ba3f45f 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -152,8 +152,8 @@ También puedes acceder a los modelos de Go a través de los siguientes endpoint | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 80cd5d2fc..51897e8a4 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -140,8 +140,8 @@ Vous pouvez également accéder aux modèles Go via les points de terminaison d' | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index 9db12a644..8e85fb03c 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -152,8 +152,8 @@ You can also access Go models through the following API endpoints. | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index 775e567f8..03c1c10ca 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -150,8 +150,8 @@ Puoi anche accedere ai modelli Go tramite i seguenti endpoint API. | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index 6ab9c3235..1a110a700 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -140,8 +140,8 @@ Zen残高にクレジットがある場合は、コンソールで**Use balance* | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index 83c7c8e6a..445cd8657 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -140,8 +140,8 @@ Zen 잔액에 크레딧도 있다면, console에서 **Use balance** 옵션을 | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index 53ad74f37..ab3676d6d 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -152,8 +152,8 @@ Du kan også få tilgang til Go-modeller gjennom følgende API-endepunkter. | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index 87fcf1cc3..b984f0859 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -144,8 +144,8 @@ Możesz również uzyskać dostęp do modeli Go za pośrednictwem następującyc | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index 4a2208933..2094b5a57 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -152,8 +152,8 @@ Você também pode acessar os modelos do Go através dos seguintes endpoints de | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index 5a07418a7..0c257153b 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -152,8 +152,8 @@ OpenCode Go включает следующие лимиты: | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index 17cd9feb8..153f61368 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -140,8 +140,8 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index 884c0e00c..77b0eb8c7 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -140,8 +140,8 @@ Go modellerine aşağıdaki API uç noktaları aracılığıyla da erişebilirsi | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index 828da590a..04156059a 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -140,8 +140,8 @@ OpenCode Go 包含以下限制: | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index 1df68a6c4..a466ccba3 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -140,8 +140,8 @@ OpenCode Go 包含以下限制: | GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Pro | mimo-v2-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo-V2.5-Pro | mimo-v2.5-pro | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | From d03e6cedde09d3e7ec31ea944d774176160c5b78 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 26 Apr 2026 11:18:47 -0400 Subject: [PATCH 207/426] ci: update team assignments in github-triage Update team member assignments in the triage tool: - Remove thdxr from tui and core teams - Add simonklee to tui team - Add kitlangton to core team --- .opencode/tool/github-triage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.opencode/tool/github-triage.ts b/.opencode/tool/github-triage.ts index dcbfc8d05..56886808a 100644 --- a/.opencode/tool/github-triage.ts +++ b/.opencode/tool/github-triage.ts @@ -3,8 +3,8 @@ import { tool } from "@opencode-ai/plugin" const TEAM = { desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"], zen: ["fwang", "MrMushrooooom"], - tui: ["thdxr", "kommander", "rekram1-node"], - core: ["thdxr", "rekram1-node", "jlongster"], + tui: ["kommander", "rekram1-node", "simonklee"], + core: ["kitlangton", "rekram1-node", "jlongster"], docs: ["R44VC0RP"], windows: ["Hona"], } as const From da61b0290a63cecaab5739431d9ed9eb9967e528 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 26 Apr 2026 11:31:46 -0400 Subject: [PATCH 208/426] feat(httpapi): bridge sync routes (#24484) --- packages/opencode/specs/effect/http-api.md | 42 +++--- .../src/server/routes/instance/httpapi/mcp.ts | 7 +- .../server/routes/instance/httpapi/server.ts | 2 + .../server/routes/instance/httpapi/sync.ts | 130 ++++++++++++++++++ .../routes/instance/httpapi/workspace.ts | 2 +- .../src/server/routes/instance/index.ts | 4 + .../opencode/test/server/httpapi-sync.test.ts | 84 +++++++++++ 7 files changed, 246 insertions(+), 25 deletions(-) create mode 100644 packages/opencode/src/server/routes/instance/httpapi/sync.ts create mode 100644 packages/opencode/test/server/httpapi-sync.test.ts diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 2a141d028..932cf8f67 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -170,23 +170,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------- | ----------------- | -------------------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` | read, providers, update | -| `project` | `bridged` | list, current, git init, update | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | -| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | -| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `later` | process/control side effects | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| Area | Status | Notes | +| ------------------------- | ----------------- | ---------------------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` | read, providers, update | +| `project` | `bridged` | list, current, git init, update | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | +| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | +| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | +| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `bridged` | start/replay/history | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Full Route Checklist @@ -280,9 +280,9 @@ This checklist tracks bridge parity only. Checked routes are available through t ### Sync Routes -- [ ] `POST /sync/start` - start workspace sync. -- [ ] `POST /sync/replay` - replay sync events. -- [ ] `POST /sync/history` - list sync event history. +- [x] `POST /sync/start` - start workspace sync. +- [x] `POST /sync/replay` - replay sync events. +- [x] `POST /sync/history` - list sync event history. ### Session Routes @@ -353,7 +353,7 @@ Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays rev 4. [x] Bridge experimental console switch and tool list routes. 5. [x] Bridge experimental global session list. 6. [x] Bridge workspace create/remove/session-restore routes. -7. [ ] Bridge sync start/replay/history routes. +7. [x] Bridge sync start/replay/history routes. 8. [ ] Bridge session read routes: list, status, get, children, todo, diff, messages. 9. [ ] Bridge session lifecycle mutation routes: create, delete, update, fork, abort. 10. [ ] Bridge session share/summary/message/part mutation routes. diff --git a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts index 2abc8519a..b7b8d512f 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts @@ -139,9 +139,10 @@ export const mcpHandlers = Layer.unwrap( }) const add = Effect.fn("McpHttpApi.add")(function* (ctx: { payload: typeof AddPayload.Type }) { - const payload = Schema.decodeUnknownSync(AddPayload)(ctx.payload) - const result = (yield* mcp.add(payload.name, payload.config)).status - return Schema.decodeUnknownSync(StatusMap)("status" in result ? { [payload.name]: result } : result) + const result = (yield* mcp.add(ctx.payload.name, ctx.payload.config)).status + return yield* Schema.decodeUnknownEffect(StatusMap)("status" in result ? { [ctx.payload.name]: result } : result).pipe( + Effect.mapError(() => new HttpApiError.BadRequest({})), + ) }) const authStart = Effect.fn("McpHttpApi.authStart")(function* (ctx: { params: { name: string } }) { diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index be574c914..fa728b04e 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -18,6 +18,7 @@ import { PermissionApi, permissionHandlers } from "./permission" import { ProjectApi, projectHandlers } from "./project" import { ProviderApi, providerHandlers } from "./provider" import { QuestionApi, questionHandlers } from "./question" +import { SyncApi, syncHandlers } from "./sync" import { WorkspaceApi, workspaceHandlers } from "./workspace" import { disposeMiddleware } from "./lifecycle" import { memoMap } from "@opencode-ai/core/effect/memo-map" @@ -73,6 +74,7 @@ export const routes = Layer.mergeAll( HttpApiBuilder.layer(QuestionApi).pipe(Layer.provide(questionHandlers)), HttpApiBuilder.layer(PermissionApi).pipe(Layer.provide(permissionHandlers)), HttpApiBuilder.layer(ProviderApi).pipe(Layer.provide(providerHandlers)), + HttpApiBuilder.layer(SyncApi).pipe(Layer.provide(syncHandlers)), HttpApiBuilder.layer(WorkspaceApi).pipe(Layer.provide(workspaceHandlers)), ).pipe( Layer.provide(authorizationLayer), diff --git a/packages/opencode/src/server/routes/instance/httpapi/sync.ts b/packages/opencode/src/server/routes/instance/httpapi/sync.ts new file mode 100644 index 000000000..aa213cc16 --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/sync.ts @@ -0,0 +1,130 @@ +import { startWorkspaceSyncing } from "@/control-plane/workspace" +import * as InstanceState from "@/effect/instance-state" +import { Database, asc, and, eq, lte, not, or } from "@/storage" +import { SyncEvent } from "@/sync" +import { EventTable } from "@/sync/event.sql" +import { Effect, Layer, Schema } from "effect" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" + +const root = "/sync" +const ReplayEvent = Schema.Struct({ + id: Schema.String, + aggregateID: Schema.String, + seq: Schema.Number, + type: Schema.String, + data: Schema.Record(Schema.String, Schema.Unknown), +}).annotate({ identifier: "SyncReplayEvent" }) +const ReplayPayload = Schema.Struct({ + directory: Schema.String, + events: Schema.NonEmptyArray(ReplayEvent), +}).annotate({ identifier: "SyncReplayInput" }) +const ReplayResponse = Schema.Struct({ + sessionID: Schema.String, +}).annotate({ identifier: "SyncReplayResponse" }) +const HistoryPayload = Schema.Record(Schema.String, Schema.Number) +const HistoryEvent = Schema.Struct({ + id: Schema.String, + aggregate_id: Schema.String, + seq: Schema.Number, + type: Schema.String, + data: Schema.Record(Schema.String, Schema.Unknown), +}).annotate({ identifier: "SyncHistoryEvent" }) + +export const SyncPaths = { + start: `${root}/start`, + replay: `${root}/replay`, + history: `${root}/history`, +} as const + +export const SyncApi = HttpApi.make("sync") + .add( + HttpApiGroup.make("sync") + .add( + HttpApiEndpoint.post("start", SyncPaths.start, { + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "sync.start", + summary: "Start workspace sync", + description: "Start sync loops for workspaces in the current project that have active sessions.", + }), + ), + HttpApiEndpoint.post("replay", SyncPaths.replay, { + payload: ReplayPayload, + success: ReplayResponse, + }).annotateMerge( + OpenApi.annotations({ + identifier: "sync.replay", + summary: "Replay sync events", + description: "Validate and replay a complete sync event history.", + }), + ), + HttpApiEndpoint.post("history", SyncPaths.history, { + payload: HistoryPayload, + success: Schema.Array(HistoryEvent), + }).annotateMerge( + OpenApi.annotations({ + identifier: "sync.history.list", + summary: "List sync events", + description: + "List sync events for all aggregates. Keys are aggregate IDs the client already knows about, values are the last known sequence ID. Events with seq > value are returned for those aggregates. Aggregates not listed in the input get their full history.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "sync", + description: "Experimental HttpApi sync routes.", + }), + ) + .middleware(Authorization), + ) + .annotateMerge( + OpenApi.annotations({ + title: "opencode experimental HttpApi", + version: "0.0.1", + description: "Experimental HttpApi surface for selected instance routes.", + }), + ) + +export const syncHandlers = Layer.unwrap( + Effect.gen(function* () { + const start = Effect.fn("SyncHttpApi.start")(function* () { + startWorkspaceSyncing((yield* InstanceState.context).project.id) + return true + }) + + const replay = Effect.fn("SyncHttpApi.replay")(function* (ctx: { payload: typeof ReplayPayload.Type }) { + const events: SyncEvent.SerializedEvent[] = ctx.payload.events.map((event) => ({ + id: event.id, + aggregateID: event.aggregateID, + seq: event.seq, + type: event.type, + data: { ...event.data }, + })) + SyncEvent.replayAll(events) + return { sessionID: events[0].aggregateID } + }) + + const history = Effect.fn("SyncHttpApi.history")(function* (ctx: { payload: typeof HistoryPayload.Type }) { + const exclude = Object.entries(ctx.payload) + return Database.use((db) => + db + .select() + .from(EventTable) + .where( + exclude.length > 0 + ? not(or(...exclude.map(([id, seq]) => and(eq(EventTable.aggregate_id, id), lte(EventTable.seq, seq))))!) + : undefined, + ) + .orderBy(asc(EventTable.seq)) + .all(), + ) + }) + + return HttpApiBuilder.group(SyncApi, "sync", (handlers) => + handlers.handle("start", start).handle("replay", replay).handle("history", history), + ) + }), +) diff --git a/packages/opencode/src/server/routes/instance/httpapi/workspace.ts b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts index c47a00d6b..c26959601 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/workspace.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/workspace.ts @@ -123,7 +123,7 @@ export const workspaceHandlers = Layer.unwrap( return yield* Effect.promise(() => Instance.restore(instance, () => Workspace.create({ - ...Schema.decodeUnknownSync(CreatePayload)(ctx.payload), + ...ctx.payload, projectID: instance.project.id, }), ), diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 58e64443d..57988b26f 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -20,6 +20,7 @@ import { ExperimentalPaths } from "./httpapi/experimental" import { FilePaths } from "./httpapi/file" import { InstancePaths } from "./httpapi/instance" import { McpPaths } from "./httpapi/mcp" +import { SyncPaths } from "./httpapi/sync" import { ProjectRoutes } from "./project" import { SessionRoutes } from "./session" import { PtyRoutes } from "./pty" @@ -89,6 +90,9 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.delete(McpPaths.auth, (c) => handler(c.req.raw, context)) app.post(McpPaths.connect, (c) => handler(c.req.raw, context)) app.post(McpPaths.disconnect, (c) => handler(c.req.raw, context)) + app.post(SyncPaths.start, (c) => handler(c.req.raw, context)) + app.post(SyncPaths.replay, (c) => handler(c.req.raw, context)) + app.post(SyncPaths.history, (c) => handler(c.req.raw, context)) } return app diff --git a/packages/opencode/test/server/httpapi-sync.test.ts b/packages/opencode/test/server/httpapi-sync.test.ts new file mode 100644 index 000000000..d48d7608a --- /dev/null +++ b/packages/opencode/test/server/httpapi-sync.test.ts @@ -0,0 +1,84 @@ +import { afterEach, describe, expect, test } from "bun:test" +import type { UpgradeWebSocket } from "hono/ws" +import { Effect } from "effect" +import { Flag } from "@opencode-ai/core/flag/flag" +import { Instance } from "../../src/project/instance" +import { InstanceRoutes } from "../../src/server/routes/instance" +import { SyncPaths } from "../../src/server/routes/instance/httpapi/sync" +import { Session } from "../../src/session" +import { Log } from "../../src/util" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" + +void Log.init({ print: false }) + +const originalHttpApi = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI +const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES +const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket + +function app() { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true + return InstanceRoutes(websocket) +} + +function runSession(fx: Effect.Effect) { + return Effect.runPromise(fx.pipe(Effect.provide(Session.defaultLayer))) +} + +afterEach(async () => { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = originalHttpApi + Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = originalWorkspaces + await Instance.disposeAll() + await resetDatabase() +}) + +describe("sync HttpApi", () => { + test("serves sync routes through Hono bridge", async () => { + Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = true + await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) + const headers = { "x-opencode-directory": tmp.path, "content-type": "application/json" } + + const session = await Instance.provide({ + directory: tmp.path, + fn: async () => runSession(Session.Service.use((svc) => svc.create({ title: "sync" }))), + }) + + const started = await app().request(SyncPaths.start, { method: "POST", headers }) + expect(started.status).toBe(200) + expect(await started.json()).toBe(true) + + const history = await app().request(SyncPaths.history, { + method: "POST", + headers, + body: JSON.stringify({}), + }) + expect(history.status).toBe(200) + const rows = (await history.json()) as Array<{ + id: string + aggregate_id: string + seq: number + type: string + data: Record + }> + expect(rows.map((row) => row.aggregate_id)).toContain(session.id) + + const replayed = await app().request(SyncPaths.replay, { + method: "POST", + headers, + body: JSON.stringify({ + directory: tmp.path, + events: rows + .filter((row) => row.aggregate_id === session.id) + .map((row) => ({ + id: row.id, + aggregateID: row.aggregate_id, + seq: row.seq, + type: row.type, + data: row.data, + })), + }), + }) + expect(replayed.status).toBe(200) + expect(await replayed.json()).toEqual({ sessionID: session.id }) + }) +}) From de413c56ae543a9276e9019b64053a4f626d0aec Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 15:32:42 +0000 Subject: [PATCH 209/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 34 +++++++++---------- .../src/server/routes/instance/httpapi/mcp.ts | 6 ++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 932cf8f67..3a1a99860 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -170,23 +170,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------- | ----------------- | ---------------------------------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` | read, providers, update | -| `project` | `bridged` | list, current, git init, update | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | -| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | -| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `bridged` | start/replay/history | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| Area | Status | Notes | +| ------------------------- | ----------------- | -------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` | read, providers, update | +| `project` | `bridged` | list, current, git init, update | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | +| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | +| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | +| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | +| `session` | `later/special` | large stateful surface plus streaming | +| `sync` | `bridged` | start/replay/history | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Full Route Checklist diff --git a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts index b7b8d512f..692187d73 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/mcp.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/mcp.ts @@ -140,9 +140,9 @@ export const mcpHandlers = Layer.unwrap( const add = Effect.fn("McpHttpApi.add")(function* (ctx: { payload: typeof AddPayload.Type }) { const result = (yield* mcp.add(ctx.payload.name, ctx.payload.config)).status - return yield* Schema.decodeUnknownEffect(StatusMap)("status" in result ? { [ctx.payload.name]: result } : result).pipe( - Effect.mapError(() => new HttpApiError.BadRequest({})), - ) + return yield* Schema.decodeUnknownEffect(StatusMap)( + "status" in result ? { [ctx.payload.name]: result } : result, + ).pipe(Effect.mapError(() => new HttpApiError.BadRequest({}))) }) const authStart = Effect.fn("McpHttpApi.authStart")(function* (ctx: { params: { name: string } }) { From e0d1ff42c0df9041a204cfb0755a0d259b35de3c Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 26 Apr 2026 11:49:11 -0400 Subject: [PATCH 210/426] feat(httpapi): bridge session read routes (#24485) --- packages/opencode/specs/effect/http-api.md | 52 ++-- .../server/routes/instance/httpapi/server.ts | 2 + .../server/routes/instance/httpapi/session.ts | 242 ++++++++++++++++++ .../src/server/routes/instance/index.ts | 9 + .../test/server/httpapi-session.test.ts | 108 ++++++++ 5 files changed, 387 insertions(+), 26 deletions(-) create mode 100644 packages/opencode/src/server/routes/instance/httpapi/session.ts create mode 100644 packages/opencode/test/server/httpapi-session.test.ts diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 3a1a99860..fc17feb5b 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -170,23 +170,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------- | ----------------- | -------------------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` | read, providers, update | -| `project` | `bridged` | list, current, git init, update | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | -| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | -| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | -| `session` | `later/special` | large stateful surface plus streaming | -| `sync` | `bridged` | start/replay/history | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| Area | Status | Notes | +| ------------------------- | ----------------- | ---------------------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` | read, providers, update | +| `project` | `bridged` | list, current, git init, update | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | +| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | +| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | +| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | +| `session` | `bridged` partial | read routes; lifecycle, message mutations, streaming remain | +| `sync` | `bridged` | start/replay/history | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Full Route Checklist @@ -286,11 +286,11 @@ This checklist tracks bridge parity only. Checked routes are available through t ### Session Routes -- [ ] `GET /session` - list sessions. -- [ ] `GET /session/status` - session status map. -- [ ] `GET /session/:sessionID` - get session. -- [ ] `GET /session/:sessionID/children` - get child sessions. -- [ ] `GET /session/:sessionID/todo` - get session todos. +- [x] `GET /session` - list sessions. +- [x] `GET /session/status` - session status map. +- [x] `GET /session/:sessionID` - get session. +- [x] `GET /session/:sessionID/children` - get child sessions. +- [x] `GET /session/:sessionID/todo` - get session todos. - [ ] `POST /session` - create session. - [ ] `DELETE /session/:sessionID` - delete session. - [ ] `PATCH /session/:sessionID` - update session metadata. @@ -298,11 +298,11 @@ This checklist tracks bridge parity only. Checked routes are available through t - [ ] `POST /session/:sessionID/fork` - fork session. - [ ] `POST /session/:sessionID/abort` - abort session. - [ ] `POST /session/:sessionID/share` - share session. -- [ ] `GET /session/:sessionID/diff` - session diff. +- [x] `GET /session/:sessionID/diff` - session diff. - [ ] `DELETE /session/:sessionID/share` - unshare session. - [ ] `POST /session/:sessionID/summarize` - summarize session. -- [ ] `GET /session/:sessionID/message` - list session messages. -- [ ] `GET /session/:sessionID/message/:messageID` - get message. +- [x] `GET /session/:sessionID/message` - list session messages. +- [x] `GET /session/:sessionID/message/:messageID` - get message. - [ ] `DELETE /session/:sessionID/message/:messageID` - delete message. - [ ] `DELETE /session/:sessionID/message/:messageID/part/:partID` - delete part. - [ ] `PATCH /session/:sessionID/message/:messageID/part/:partID` - update part. @@ -354,7 +354,7 @@ Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays rev 5. [x] Bridge experimental global session list. 6. [x] Bridge workspace create/remove/session-restore routes. 7. [x] Bridge sync start/replay/history routes. -8. [ ] Bridge session read routes: list, status, get, children, todo, diff, messages. +8. [x] Bridge session read routes: list, status, get, children, todo, diff, messages. 9. [ ] Bridge session lifecycle mutation routes: create, delete, update, fork, abort. 10. [ ] Bridge session share/summary/message/part mutation routes. 11. [ ] Replace event SSE with non-Hono Effect HTTP. diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index fa728b04e..adc70a43b 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -18,6 +18,7 @@ import { PermissionApi, permissionHandlers } from "./permission" import { ProjectApi, projectHandlers } from "./project" import { ProviderApi, providerHandlers } from "./provider" import { QuestionApi, questionHandlers } from "./question" +import { SessionApi, sessionHandlers } from "./session" import { SyncApi, syncHandlers } from "./sync" import { WorkspaceApi, workspaceHandlers } from "./workspace" import { disposeMiddleware } from "./lifecycle" @@ -74,6 +75,7 @@ export const routes = Layer.mergeAll( HttpApiBuilder.layer(QuestionApi).pipe(Layer.provide(questionHandlers)), HttpApiBuilder.layer(PermissionApi).pipe(Layer.provide(permissionHandlers)), HttpApiBuilder.layer(ProviderApi).pipe(Layer.provide(providerHandlers)), + HttpApiBuilder.layer(SessionApi).pipe(Layer.provide(sessionHandlers)), HttpApiBuilder.layer(SyncApi).pipe(Layer.provide(syncHandlers)), HttpApiBuilder.layer(WorkspaceApi).pipe(Layer.provide(workspaceHandlers)), ).pipe( diff --git a/packages/opencode/src/server/routes/instance/httpapi/session.ts b/packages/opencode/src/server/routes/instance/httpapi/session.ts new file mode 100644 index 000000000..e06c8d98a --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/session.ts @@ -0,0 +1,242 @@ +import * as InstanceState from "@/effect/instance-state" +import { Instance } from "@/project/instance" +import { Session } from "@/session" +import { MessageV2 } from "@/session/message-v2" +import { SessionStatus } from "@/session/status" +import { SessionSummary } from "@/session/summary" +import { Todo } from "@/session/todo" +import { MessageID, SessionID } from "@/session/schema" +import { Snapshot } from "@/snapshot" +import { Effect, Layer, Schema, Struct } from "effect" +import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http" +import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { Authorization } from "./auth" + +const root = "/session" +const ListQuery = Schema.Struct({ + directory: Schema.optional(Schema.String), + roots: Schema.optional(Schema.Literals(["true", "false"])), + start: Schema.optional(Schema.NumberFromString), + search: Schema.optional(Schema.String), + limit: Schema.optional(Schema.NumberFromString), +}) +const DiffQuery = Schema.Struct(Struct.omit(SessionSummary.DiffInput.fields, ["sessionID"])) +const MessagesQuery = Schema.Struct({ + limit: Schema.optional(Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))), + before: Schema.optional(Schema.String), +}) +const StatusMap = Schema.Record(Schema.String, SessionStatus.Info) + +export const SessionPaths = { + list: root, + status: `${root}/status`, + get: `${root}/:sessionID`, + children: `${root}/:sessionID/children`, + todo: `${root}/:sessionID/todo`, + diff: `${root}/:sessionID/diff`, + messages: `${root}/:sessionID/message`, + message: `${root}/:sessionID/message/:messageID`, +} as const + +export const SessionApi = HttpApi.make("session") + .add( + HttpApiGroup.make("session") + .add( + HttpApiEndpoint.get("list", SessionPaths.list, { + query: ListQuery, + success: Schema.Array(Session.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.list", + summary: "List sessions", + description: "Get a list of all OpenCode sessions, sorted by most recently updated.", + }), + ), + HttpApiEndpoint.get("status", SessionPaths.status, { + success: StatusMap, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.status", + summary: "Get session status", + description: "Retrieve the current status of all sessions, including active, idle, and completed states.", + }), + ), + HttpApiEndpoint.get("get", SessionPaths.get, { + params: { sessionID: SessionID }, + success: Session.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.get", + summary: "Get session", + description: "Retrieve detailed information about a specific OpenCode session.", + }), + ), + HttpApiEndpoint.get("children", SessionPaths.children, { + params: { sessionID: SessionID }, + success: Schema.Array(Session.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.children", + summary: "Get session children", + description: "Retrieve all child sessions that were forked from the specified parent session.", + }), + ), + HttpApiEndpoint.get("todo", SessionPaths.todo, { + params: { sessionID: SessionID }, + success: Schema.Array(Todo.Info), + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.todo", + summary: "Get session todos", + description: "Retrieve the todo list associated with a specific session, showing tasks and action items.", + }), + ), + HttpApiEndpoint.get("diff", SessionPaths.diff, { + params: { sessionID: SessionID }, + query: DiffQuery, + success: Schema.Array(Snapshot.FileDiff), + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.diff", + summary: "Get message diff", + description: "Get the file changes (diff) that resulted from a specific user message in the session.", + }), + ), + HttpApiEndpoint.get("messages", SessionPaths.messages, { + params: { sessionID: SessionID }, + query: MessagesQuery, + success: Schema.Array(MessageV2.WithParts), + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.messages", + summary: "Get session messages", + description: "Retrieve all messages in a session, including user prompts and AI responses.", + }), + ), + HttpApiEndpoint.get("message", SessionPaths.message, { + params: { sessionID: SessionID, messageID: MessageID }, + success: MessageV2.WithParts, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.message", + summary: "Get message", + description: "Retrieve a specific message from a session by its message ID.", + }), + ), + ) + .annotateMerge( + OpenApi.annotations({ + title: "session", + description: "Experimental HttpApi session routes.", + }), + ) + .middleware(Authorization), + ) + .annotateMerge( + OpenApi.annotations({ + title: "opencode experimental HttpApi", + version: "0.0.1", + description: "Experimental HttpApi surface for selected instance routes.", + }), + ) + +export const sessionHandlers = Layer.unwrap( + Effect.gen(function* () { + const session = yield* Session.Service + const statusSvc = yield* SessionStatus.Service + const todoSvc = yield* Todo.Service + const summary = yield* SessionSummary.Service + + const list = Effect.fn("SessionHttpApi.list")(function* (ctx: { query: typeof ListQuery.Type }) { + const instance = yield* InstanceState.context + return Instance.restore(instance, () => + Array.from( + Session.list({ + directory: ctx.query.directory, + roots: ctx.query.roots === "true" ? true : undefined, + start: ctx.query.start, + search: ctx.query.search, + limit: ctx.query.limit, + }), + ), + ) + }) + + const status = Effect.fn("SessionHttpApi.status")(function* () { + return Object.fromEntries(yield* statusSvc.list()) + }) + + const get = Effect.fn("SessionHttpApi.get")(function* (ctx: { params: { sessionID: SessionID } }) { + return yield* session.get(ctx.params.sessionID) + }) + + const children = Effect.fn("SessionHttpApi.children")(function* (ctx: { params: { sessionID: SessionID } }) { + return yield* session.children(ctx.params.sessionID) + }) + + const todo = Effect.fn("SessionHttpApi.todo")(function* (ctx: { params: { sessionID: SessionID } }) { + return yield* todoSvc.get(ctx.params.sessionID) + }) + + const diff = Effect.fn("SessionHttpApi.diff")(function* (ctx: { + params: { sessionID: SessionID } + query: typeof DiffQuery.Type + }) { + return yield* summary.diff({ sessionID: ctx.params.sessionID, messageID: ctx.query.messageID }) + }) + + const messages = Effect.fn("SessionHttpApi.messages")(function* (ctx: { + params: { sessionID: SessionID } + query: typeof MessagesQuery.Type + }) { + if (ctx.query.limit === undefined || ctx.query.limit === 0) { + yield* session.get(ctx.params.sessionID) + return yield* session.messages({ sessionID: ctx.params.sessionID }) + } + + const page = MessageV2.page({ + sessionID: ctx.params.sessionID, + limit: ctx.query.limit, + before: ctx.query.before, + }) + if (!page.cursor) return page.items + + const request = yield* HttpServerRequest.HttpServerRequest + const url = new URL(request.url, "http://localhost") + url.searchParams.set("limit", ctx.query.limit.toString()) + url.searchParams.set("before", page.cursor) + return HttpServerResponse.jsonUnsafe(page.items, { + headers: { + "Access-Control-Expose-Headers": "Link, X-Next-Cursor", + Link: `<${url.toString()}>; rel="next"`, + "X-Next-Cursor": page.cursor, + }, + }) + }) + + const message = Effect.fn("SessionHttpApi.message")(function* (ctx: { + params: { sessionID: SessionID; messageID: MessageID } + }) { + return yield* Effect.sync(() => + MessageV2.get({ sessionID: ctx.params.sessionID, messageID: ctx.params.messageID }), + ) + }) + + return HttpApiBuilder.group(SessionApi, "session", (handlers) => + handlers + .handle("list", list) + .handle("status", status) + .handle("get", get) + .handle("children", children) + .handle("todo", todo) + .handle("diff", diff) + .handle("messages", messages) + .handle("message", message), + ) + }), +).pipe( + Layer.provide(Session.defaultLayer), + Layer.provide(SessionStatus.defaultLayer), + Layer.provide(Todo.defaultLayer), + Layer.provide(SessionSummary.defaultLayer), +) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 57988b26f..965f26b48 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -20,6 +20,7 @@ import { ExperimentalPaths } from "./httpapi/experimental" import { FilePaths } from "./httpapi/file" import { InstancePaths } from "./httpapi/instance" import { McpPaths } from "./httpapi/mcp" +import { SessionPaths } from "./httpapi/session" import { SyncPaths } from "./httpapi/sync" import { ProjectRoutes } from "./project" import { SessionRoutes } from "./session" @@ -93,6 +94,14 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.post(SyncPaths.start, (c) => handler(c.req.raw, context)) app.post(SyncPaths.replay, (c) => handler(c.req.raw, context)) app.post(SyncPaths.history, (c) => handler(c.req.raw, context)) + app.get(SessionPaths.list, (c) => handler(c.req.raw, context)) + app.get(SessionPaths.status, (c) => handler(c.req.raw, context)) + app.get(SessionPaths.get, (c) => handler(c.req.raw, context)) + app.get(SessionPaths.children, (c) => handler(c.req.raw, context)) + app.get(SessionPaths.todo, (c) => handler(c.req.raw, context)) + app.get(SessionPaths.diff, (c) => handler(c.req.raw, context)) + app.get(SessionPaths.messages, (c) => handler(c.req.raw, context)) + app.get(SessionPaths.message, (c) => handler(c.req.raw, context)) } return app diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts new file mode 100644 index 000000000..42cbb8495 --- /dev/null +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -0,0 +1,108 @@ +import { afterEach, describe, expect, test } from "bun:test" +import type { UpgradeWebSocket } from "hono/ws" +import { Effect } from "effect" +import { Flag } from "@opencode-ai/core/flag/flag" +import { ModelID, ProviderID } from "../../src/provider/schema" +import { Instance } from "../../src/project/instance" +import { InstanceRoutes } from "../../src/server/routes/instance" +import { SessionPaths } from "../../src/server/routes/instance/httpapi/session" +import { Session } from "../../src/session" +import { MessageID, PartID, type SessionID } from "../../src/session/schema" +import { MessageV2 } from "../../src/session/message-v2" +import { Log } from "../../src/util" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" + +void Log.init({ print: false }) + +const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI +const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket + +function app() { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true + return InstanceRoutes(websocket) +} + +function runSession(fx: Effect.Effect) { + return Effect.runPromise(fx.pipe(Effect.provide(Session.defaultLayer))) +} + +function pathFor(path: string, params: Record) { + return Object.entries(params).reduce((result, [key, value]) => result.replace(`:${key}`, value), path) +} + +async function createSession(directory: string, input?: Session.CreateInput) { + return Instance.provide({ + directory, + fn: async () => runSession(Session.Service.use((svc) => svc.create(input))), + }) +} + +async function createTextMessage(directory: string, sessionID: SessionID, text: string) { + return Instance.provide({ + directory, + fn: async () => + runSession( + Effect.gen(function* () { + const svc = yield* Session.Service + const info = yield* svc.updateMessage({ + id: MessageID.ascending(), + role: "user", + sessionID, + agent: "build", + model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") }, + time: { created: Date.now() }, + }) + yield* svc.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: info.id, + type: "text", + text, + }) + return info + }), + ), + }) +} + +async function json(response: Response) { + if (response.status !== 200) throw new Error(await response.text()) + return (await response.json()) as T +} + +afterEach(async () => { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original + await Instance.disposeAll() + await resetDatabase() +}) + +describe("session HttpApi", () => { + test("serves read routes through Hono bridge", async () => { + await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) + const headers = { "x-opencode-directory": tmp.path } + const parent = await createSession(tmp.path, { title: "parent" }) + const child = await createSession(tmp.path, { title: "child", parentID: parent.id }) + const message = await createTextMessage(tmp.path, parent.id, "hello") + await createTextMessage(tmp.path, parent.id, "world") + + expect((await json(await app().request(`${SessionPaths.list}?roots=true`, { headers }))).map((item) => item.id)).toContain(parent.id) + + expect(await json>(await app().request(SessionPaths.status, { headers }))).toEqual({}) + + expect(await json(await app().request(pathFor(SessionPaths.get, { sessionID: parent.id }), { headers }))).toMatchObject({ id: parent.id, title: "parent" }) + + expect((await json(await app().request(pathFor(SessionPaths.children, { sessionID: parent.id }), { headers }))).map((item) => item.id)).toEqual([child.id]) + + expect(await json(await app().request(pathFor(SessionPaths.todo, { sessionID: parent.id }), { headers }))).toEqual([]) + + expect(await json(await app().request(pathFor(SessionPaths.diff, { sessionID: parent.id }), { headers }))).toEqual([]) + + const messages = await app().request(`${pathFor(SessionPaths.messages, { sessionID: parent.id })}?limit=1`, { headers }) + const messagePage = await json(messages) + expect(messages.headers.get("x-next-cursor")).toBeTruthy() + expect(messagePage[0]?.parts[0]).toMatchObject({ type: "text" }) + + expect(await json(await app().request(pathFor(SessionPaths.message, { sessionID: parent.id, messageID: message.id }), { headers }))).toMatchObject({ info: { id: message.id } }) + }) +}) From daff119fe453207cdb2c14878d87e407dbd05de0 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 15:50:09 +0000 Subject: [PATCH 211/426] chore: generate --- packages/opencode/specs/effect/http-api.md | 34 ++++++++--------- .../test/server/httpapi-session.test.ts | 38 +++++++++++++++---- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index fc17feb5b..f6a0c06a5 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -170,23 +170,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho ## Current Route Status -| Area | Status | Notes | -| ------------------------- | ----------------- | ---------------------------------------------------------------------------------------- | -| `question` | `bridged` | `GET /question`, reply, reject | -| `permission` | `bridged` | list and reply | -| `provider` | `bridged` | list, auth, OAuth authorize/callback | -| `config` | `bridged` | read, providers, update | -| `project` | `bridged` | list, current, git init, update | -| `file` | `bridged` partial | find text/file/symbol, list/content/status | -| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | -| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | -| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | -| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | -| `session` | `bridged` partial | read routes; lifecycle, message mutations, streaming remain | -| `sync` | `bridged` | start/replay/history | -| `event` | `special` | SSE | -| `pty` | `special` | websocket | -| `tui` | `special` | UI bridge | +| Area | Status | Notes | +| ------------------------- | ----------------- | -------------------------------------------------------------------------- | +| `question` | `bridged` | `GET /question`, reply, reject | +| `permission` | `bridged` | list and reply | +| `provider` | `bridged` | list, auth, OAuth authorize/callback | +| `config` | `bridged` | read, providers, update | +| `project` | `bridged` | list, current, git init, update | +| `file` | `bridged` partial | find text/file/symbol, list/content/status | +| `mcp` | `bridged` | status, add, OAuth, connect/disconnect | +| `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | +| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | +| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | +| `session` | `bridged` partial | read routes; lifecycle, message mutations, streaming remain | +| `sync` | `bridged` | start/replay/history | +| `event` | `special` | SSE | +| `pty` | `special` | websocket | +| `tui` | `special` | UI bridge | ## Full Route Checklist diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index 42cbb8495..4c4663d71 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -86,23 +86,47 @@ describe("session HttpApi", () => { const message = await createTextMessage(tmp.path, parent.id, "hello") await createTextMessage(tmp.path, parent.id, "world") - expect((await json(await app().request(`${SessionPaths.list}?roots=true`, { headers }))).map((item) => item.id)).toContain(parent.id) + expect( + (await json(await app().request(`${SessionPaths.list}?roots=true`, { headers }))).map( + (item) => item.id, + ), + ).toContain(parent.id) expect(await json>(await app().request(SessionPaths.status, { headers }))).toEqual({}) - expect(await json(await app().request(pathFor(SessionPaths.get, { sessionID: parent.id }), { headers }))).toMatchObject({ id: parent.id, title: "parent" }) + expect( + await json(await app().request(pathFor(SessionPaths.get, { sessionID: parent.id }), { headers })), + ).toMatchObject({ id: parent.id, title: "parent" }) - expect((await json(await app().request(pathFor(SessionPaths.children, { sessionID: parent.id }), { headers }))).map((item) => item.id)).toEqual([child.id]) + expect( + ( + await json( + await app().request(pathFor(SessionPaths.children, { sessionID: parent.id }), { headers }), + ) + ).map((item) => item.id), + ).toEqual([child.id]) - expect(await json(await app().request(pathFor(SessionPaths.todo, { sessionID: parent.id }), { headers }))).toEqual([]) + expect( + await json(await app().request(pathFor(SessionPaths.todo, { sessionID: parent.id }), { headers })), + ).toEqual([]) - expect(await json(await app().request(pathFor(SessionPaths.diff, { sessionID: parent.id }), { headers }))).toEqual([]) + expect( + await json(await app().request(pathFor(SessionPaths.diff, { sessionID: parent.id }), { headers })), + ).toEqual([]) - const messages = await app().request(`${pathFor(SessionPaths.messages, { sessionID: parent.id })}?limit=1`, { headers }) + const messages = await app().request(`${pathFor(SessionPaths.messages, { sessionID: parent.id })}?limit=1`, { + headers, + }) const messagePage = await json(messages) expect(messages.headers.get("x-next-cursor")).toBeTruthy() expect(messagePage[0]?.parts[0]).toMatchObject({ type: "text" }) - expect(await json(await app().request(pathFor(SessionPaths.message, { sessionID: parent.id, messageID: message.id }), { headers }))).toMatchObject({ info: { id: message.id } }) + expect( + await json( + await app().request(pathFor(SessionPaths.message, { sessionID: parent.id, messageID: message.id }), { + headers, + }), + ), + ).toMatchObject({ info: { id: message.id } }) }) }) From daaa2e591129bc00aac019afec6e8a93e2ab8f30 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 26 Apr 2026 11:50:26 -0400 Subject: [PATCH 212/426] feat(httpapi): bridge session lifecycle routes (#24486) --- packages/opencode/specs/effect/http-api.md | 12 +- .../server/routes/instance/httpapi/session.ts | 160 +++++++++++++++++- .../src/server/routes/instance/index.ts | 5 + .../test/server/httpapi-session.test.ts | 44 +++++ 4 files changed, 214 insertions(+), 7 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index f6a0c06a5..e11e88b7b 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -291,12 +291,12 @@ This checklist tracks bridge parity only. Checked routes are available through t - [x] `GET /session/:sessionID` - get session. - [x] `GET /session/:sessionID/children` - get child sessions. - [x] `GET /session/:sessionID/todo` - get session todos. -- [ ] `POST /session` - create session. -- [ ] `DELETE /session/:sessionID` - delete session. -- [ ] `PATCH /session/:sessionID` - update session metadata. +- [x] `POST /session` - create session. +- [x] `DELETE /session/:sessionID` - delete session. +- [x] `PATCH /session/:sessionID` - update session metadata. - [ ] `POST /session/:sessionID/init` - run project init command. -- [ ] `POST /session/:sessionID/fork` - fork session. -- [ ] `POST /session/:sessionID/abort` - abort session. +- [x] `POST /session/:sessionID/fork` - fork session. +- [x] `POST /session/:sessionID/abort` - abort session. - [ ] `POST /session/:sessionID/share` - share session. - [x] `GET /session/:sessionID/diff` - session diff. - [ ] `DELETE /session/:sessionID/share` - unshare session. @@ -355,7 +355,7 @@ Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays rev 6. [x] Bridge workspace create/remove/session-restore routes. 7. [x] Bridge sync start/replay/history routes. 8. [x] Bridge session read routes: list, status, get, children, todo, diff, messages. -9. [ ] Bridge session lifecycle mutation routes: create, delete, update, fork, abort. +9. [x] Bridge session lifecycle mutation routes: create, delete, update, fork, abort. 10. [ ] Bridge session share/summary/message/part mutation routes. 11. [ ] Replace event SSE with non-Hono Effect HTTP. 12. [ ] Replace pty websocket/control routes with non-Hono Effect HTTP. diff --git a/packages/opencode/src/server/routes/instance/httpapi/session.ts b/packages/opencode/src/server/routes/instance/httpapi/session.ts index e06c8d98a..06f7d5791 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/session.ts @@ -1,7 +1,11 @@ import * as InstanceState from "@/effect/instance-state" +import { AppRuntime } from "@/effect/app-runtime" +import { Permission } from "@/permission" import { Instance } from "@/project/instance" +import { SessionShare } from "@/share" import { Session } from "@/session" import { MessageV2 } from "@/session/message-v2" +import { SessionPrompt } from "@/session/prompt" import { SessionStatus } from "@/session/status" import { SessionSummary } from "@/session/summary" import { Todo } from "@/session/todo" @@ -26,6 +30,18 @@ const MessagesQuery = Schema.Struct({ before: Schema.optional(Schema.String), }) const StatusMap = Schema.Record(Schema.String, SessionStatus.Info) +const UpdatePayload = Schema.Struct({ + title: Schema.optional(Schema.String), + permission: Schema.optional(Permission.Ruleset), + time: Schema.optional( + Schema.Struct({ + archived: Schema.optional(Schema.Number), + }), + ), +}).annotate({ identifier: "SessionUpdateInput" }) +const ForkPayload = Schema.Struct(Struct.omit(Session.ForkInput.fields, ["sessionID"])).annotate({ + identifier: "SessionForkInput", +}) export const SessionPaths = { list: root, @@ -36,6 +52,11 @@ export const SessionPaths = { diff: `${root}/:sessionID/diff`, messages: `${root}/:sessionID/message`, message: `${root}/:sessionID/message/:messageID`, + create: root, + remove: `${root}/:sessionID`, + update: `${root}/:sessionID`, + fork: `${root}/:sessionID/fork`, + abort: `${root}/:sessionID/abort`, } as const export const SessionApi = HttpApi.make("session") @@ -123,6 +144,58 @@ export const SessionApi = HttpApi.make("session") description: "Retrieve a specific message from a session by its message ID.", }), ), + HttpApiEndpoint.post("create", SessionPaths.create, { + payload: Session.CreateInput, + success: Session.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.create", + summary: "Create session", + description: "Create a new OpenCode session for interacting with AI assistants and managing conversations.", + }), + ), + HttpApiEndpoint.delete("remove", SessionPaths.remove, { + params: { sessionID: SessionID }, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.delete", + summary: "Delete session", + description: "Delete a session and permanently remove all associated data, including messages and history.", + }), + ), + HttpApiEndpoint.patch("update", SessionPaths.update, { + params: { sessionID: SessionID }, + payload: UpdatePayload, + success: Session.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.update", + summary: "Update session", + description: "Update properties of an existing session, such as title or other metadata.", + }), + ), + HttpApiEndpoint.post("fork", SessionPaths.fork, { + params: { sessionID: SessionID }, + payload: ForkPayload, + success: Session.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.fork", + summary: "Fork session", + description: "Create a new session by forking an existing session at a specific message point.", + }), + ), + HttpApiEndpoint.post("abort", SessionPaths.abort, { + params: { sessionID: SessionID }, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.abort", + summary: "Abort session", + description: "Abort an active session and stop any ongoing AI processing or command execution.", + }), + ), ) .annotateMerge( OpenApi.annotations({ @@ -222,6 +295,86 @@ export const sessionHandlers = Layer.unwrap( ) }) + const create = Effect.fn("SessionHttpApi.create")(function* (ctx: { payload: Session.CreateInput }) { + const instance = yield* InstanceState.context + return yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise(SessionShare.Service.use((svc) => svc.create(ctx.payload)).pipe(Effect.provide(SessionShare.defaultLayer))), + ), + ) + }) + + const remove = Effect.fn("SessionHttpApi.remove")(function* (ctx: { params: { sessionID: SessionID } }) { + const instance = yield* InstanceState.context + yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise(Session.Service.use((svc) => svc.remove(ctx.params.sessionID)).pipe(Effect.provide(Session.defaultLayer))), + ), + ) + return true + }) + + const update = Effect.fn("SessionHttpApi.update")(function* (ctx: { + params: { sessionID: SessionID } + payload: typeof UpdatePayload.Type + }) { + const instance = yield* InstanceState.context + return yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + Session.Service.use((svc) => + Effect.gen(function* () { + const current = yield* svc.get(ctx.params.sessionID) + if (ctx.payload.title !== undefined) { + yield* svc.setTitle({ sessionID: ctx.params.sessionID, title: ctx.payload.title }) + } + if (ctx.payload.permission !== undefined) { + yield* svc.setPermission({ + sessionID: ctx.params.sessionID, + permission: Permission.merge(current.permission ?? [], ctx.payload.permission), + }) + } + if (ctx.payload.time?.archived !== undefined) { + yield* svc.setArchived({ sessionID: ctx.params.sessionID, time: ctx.payload.time.archived }) + } + return yield* svc.get(ctx.params.sessionID) + }), + ).pipe(Effect.provide(Session.defaultLayer)), + ), + ), + ) + }) + + const fork = Effect.fn("SessionHttpApi.fork")(function* (ctx: { + params: { sessionID: SessionID } + payload: typeof ForkPayload.Type + }) { + const instance = yield* InstanceState.context + return yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + Session.Service.use((svc) => svc.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload.messageID })).pipe( + Effect.provide(Session.defaultLayer), + ), + ), + ), + ) + }) + + const abort = Effect.fn("SessionHttpApi.abort")(function* (ctx: { params: { sessionID: SessionID } }) { + const instance = yield* InstanceState.context + yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + SessionPrompt.Service.use((svc) => svc.cancel(ctx.params.sessionID)).pipe( + Effect.provide(SessionPrompt.defaultLayer), + ), + ), + ), + ) + return true + }) + return HttpApiBuilder.group(SessionApi, "session", (handlers) => handlers .handle("list", list) @@ -231,7 +384,12 @@ export const sessionHandlers = Layer.unwrap( .handle("todo", todo) .handle("diff", diff) .handle("messages", messages) - .handle("message", message), + .handle("message", message) + .handle("create", create) + .handle("remove", remove) + .handle("update", update) + .handle("fork", fork) + .handle("abort", abort), ) }), ).pipe( diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 965f26b48..ba029f0a3 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -102,6 +102,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.get(SessionPaths.diff, (c) => handler(c.req.raw, context)) app.get(SessionPaths.messages, (c) => handler(c.req.raw, context)) app.get(SessionPaths.message, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.create, (c) => handler(c.req.raw, context)) + app.delete(SessionPaths.remove, (c) => handler(c.req.raw, context)) + app.patch(SessionPaths.update, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.fork, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.abort, (c) => handler(c.req.raw, context)) } return app diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index 4c4663d71..d589a45a0 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -129,4 +129,48 @@ describe("session HttpApi", () => { ), ).toMatchObject({ info: { id: message.id } }) }) + + test("serves lifecycle mutation routes through Hono bridge", async () => { + await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false, share: "disabled" } }) + const headers = { "x-opencode-directory": tmp.path, "content-type": "application/json" } + + const created = await json( + await app().request(SessionPaths.create, { + method: "POST", + headers, + body: JSON.stringify({ title: "created" }), + }), + ) + expect(created.title).toBe("created") + + const updated = await json( + await app().request(pathFor(SessionPaths.update, { sessionID: created.id }), { + method: "PATCH", + headers, + body: JSON.stringify({ title: "updated", time: { archived: 1 } }), + }), + ) + expect(updated).toMatchObject({ id: created.id, title: "updated", time: { archived: 1 } }) + + const forked = await json( + await app().request(pathFor(SessionPaths.fork, { sessionID: created.id }), { + method: "POST", + headers, + body: JSON.stringify({}), + }), + ) + expect(forked.id).not.toBe(created.id) + + expect( + await json( + await app().request(pathFor(SessionPaths.abort, { sessionID: created.id }), { method: "POST", headers }), + ), + ).toBe(true) + + expect( + await json( + await app().request(pathFor(SessionPaths.remove, { sessionID: created.id }), { method: "DELETE", headers }), + ), + ).toBe(true) + }) }) From 55adcdfd0764d6070e0b410731c91818b6f095c2 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 15:51:31 +0000 Subject: [PATCH 213/426] chore: generate --- .../src/server/routes/instance/httpapi/session.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/session.ts b/packages/opencode/src/server/routes/instance/httpapi/session.ts index 06f7d5791..1db16c113 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/session.ts @@ -299,7 +299,9 @@ export const sessionHandlers = Layer.unwrap( const instance = yield* InstanceState.context return yield* Effect.promise(() => Instance.restore(instance, () => - AppRuntime.runPromise(SessionShare.Service.use((svc) => svc.create(ctx.payload)).pipe(Effect.provide(SessionShare.defaultLayer))), + AppRuntime.runPromise( + SessionShare.Service.use((svc) => svc.create(ctx.payload)).pipe(Effect.provide(SessionShare.defaultLayer)), + ), ), ) }) @@ -308,7 +310,9 @@ export const sessionHandlers = Layer.unwrap( const instance = yield* InstanceState.context yield* Effect.promise(() => Instance.restore(instance, () => - AppRuntime.runPromise(Session.Service.use((svc) => svc.remove(ctx.params.sessionID)).pipe(Effect.provide(Session.defaultLayer))), + AppRuntime.runPromise( + Session.Service.use((svc) => svc.remove(ctx.params.sessionID)).pipe(Effect.provide(Session.defaultLayer)), + ), ), ) return true @@ -353,9 +357,9 @@ export const sessionHandlers = Layer.unwrap( return yield* Effect.promise(() => Instance.restore(instance, () => AppRuntime.runPromise( - Session.Service.use((svc) => svc.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload.messageID })).pipe( - Effect.provide(Session.defaultLayer), - ), + Session.Service.use((svc) => + svc.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload.messageID }), + ).pipe(Effect.provide(Session.defaultLayer)), ), ), ) From 151df05eeb83a014b5ba568e3e169ba2663e0439 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 26 Apr 2026 12:00:02 -0400 Subject: [PATCH 214/426] feat(httpapi): bridge session message mutations (#24487) --- packages/opencode/specs/effect/http-api.md | 10 +- .../server/routes/instance/httpapi/session.ts | 150 +++++++++++++++++- .../src/server/routes/instance/index.ts | 5 + .../test/server/httpapi-session.test.ts | 54 ++++++- 4 files changed, 208 insertions(+), 11 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index e11e88b7b..3ce4aa83a 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -297,15 +297,15 @@ This checklist tracks bridge parity only. Checked routes are available through t - [ ] `POST /session/:sessionID/init` - run project init command. - [x] `POST /session/:sessionID/fork` - fork session. - [x] `POST /session/:sessionID/abort` - abort session. -- [ ] `POST /session/:sessionID/share` - share session. +- [x] `POST /session/:sessionID/share` - share session. - [x] `GET /session/:sessionID/diff` - session diff. -- [ ] `DELETE /session/:sessionID/share` - unshare session. +- [x] `DELETE /session/:sessionID/share` - unshare session. - [ ] `POST /session/:sessionID/summarize` - summarize session. - [x] `GET /session/:sessionID/message` - list session messages. - [x] `GET /session/:sessionID/message/:messageID` - get message. -- [ ] `DELETE /session/:sessionID/message/:messageID` - delete message. -- [ ] `DELETE /session/:sessionID/message/:messageID/part/:partID` - delete part. -- [ ] `PATCH /session/:sessionID/message/:messageID/part/:partID` - update part. +- [x] `DELETE /session/:sessionID/message/:messageID` - delete message. +- [x] `DELETE /session/:sessionID/message/:messageID/part/:partID` - delete part. +- [x] `PATCH /session/:sessionID/message/:messageID/part/:partID` - update part. - [ ] `POST /session/:sessionID/message` - prompt with streaming response. - [ ] `POST /session/:sessionID/prompt_async` - async prompt. - [ ] `POST /session/:sessionID/command` - run command. diff --git a/packages/opencode/src/server/routes/instance/httpapi/session.ts b/packages/opencode/src/server/routes/instance/httpapi/session.ts index 1db16c113..a028973d2 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/session.ts @@ -6,10 +6,11 @@ import { SessionShare } from "@/share" import { Session } from "@/session" import { MessageV2 } from "@/session/message-v2" import { SessionPrompt } from "@/session/prompt" +import { SessionRunState } from "@/session/run-state" import { SessionStatus } from "@/session/status" import { SessionSummary } from "@/session/summary" import { Todo } from "@/session/todo" -import { MessageID, SessionID } from "@/session/schema" +import { MessageID, PartID, SessionID } from "@/session/schema" import { Snapshot } from "@/snapshot" import { Effect, Layer, Schema, Struct } from "effect" import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http" @@ -57,6 +58,10 @@ export const SessionPaths = { update: `${root}/:sessionID`, fork: `${root}/:sessionID/fork`, abort: `${root}/:sessionID/abort`, + share: `${root}/:sessionID/share`, + deleteMessage: `${root}/:sessionID/message/:messageID`, + deletePart: `${root}/:sessionID/message/:messageID/part/:partID`, + updatePart: `${root}/:sessionID/message/:messageID/part/:partID`, } as const export const SessionApi = HttpApi.make("session") @@ -196,6 +201,56 @@ export const SessionApi = HttpApi.make("session") description: "Abort an active session and stop any ongoing AI processing or command execution.", }), ), + HttpApiEndpoint.post("share", SessionPaths.share, { + params: { sessionID: SessionID }, + success: Session.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.share", + summary: "Share session", + description: "Create a shareable link for a session, allowing others to view the conversation.", + }), + ), + HttpApiEndpoint.delete("unshare", SessionPaths.share, { + params: { sessionID: SessionID }, + success: Session.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.unshare", + summary: "Unshare session", + description: "Remove the shareable link for a session, making it private again.", + }), + ), + HttpApiEndpoint.delete("deleteMessage", SessionPaths.deleteMessage, { + params: { sessionID: SessionID, messageID: MessageID }, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.deleteMessage", + summary: "Delete message", + description: + "Permanently delete a specific message and all of its parts from a session without reverting file changes.", + }), + ), + HttpApiEndpoint.delete("deletePart", SessionPaths.deletePart, { + params: { sessionID: SessionID, messageID: MessageID, partID: PartID }, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "part.delete", + description: "Delete a part from a message.", + }), + ), + HttpApiEndpoint.patch("updatePart", SessionPaths.updatePart, { + params: { sessionID: SessionID, messageID: MessageID, partID: PartID }, + payload: MessageV2.Part, + success: MessageV2.Part, + }).annotateMerge( + OpenApi.annotations({ + identifier: "part.update", + description: "Update a part in a message.", + }), + ), ) .annotateMerge( OpenApi.annotations({ @@ -379,6 +434,91 @@ export const sessionHandlers = Layer.unwrap( return true }) + const share = Effect.fn("SessionHttpApi.share")(function* (ctx: { params: { sessionID: SessionID } }) { + const instance = yield* InstanceState.context + return yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + Effect.gen(function* () { + const share = yield* SessionShare.Service + const session = yield* Session.Service + yield* share.share(ctx.params.sessionID) + return yield* session.get(ctx.params.sessionID) + }).pipe(Effect.provide(SessionShare.defaultLayer)), + ), + ), + ) + }) + + const unshare = Effect.fn("SessionHttpApi.unshare")(function* (ctx: { params: { sessionID: SessionID } }) { + const instance = yield* InstanceState.context + return yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + Effect.gen(function* () { + const share = yield* SessionShare.Service + const session = yield* Session.Service + yield* share.unshare(ctx.params.sessionID) + return yield* session.get(ctx.params.sessionID) + }).pipe(Effect.provide(SessionShare.defaultLayer)), + ), + ), + ) + }) + + const deleteMessage = Effect.fn("SessionHttpApi.deleteMessage")(function* (ctx: { + params: { sessionID: SessionID; messageID: MessageID } + }) { + const instance = yield* InstanceState.context + yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + Effect.gen(function* () { + const state = yield* SessionRunState.Service + const session = yield* Session.Service + yield* state.assertNotBusy(ctx.params.sessionID) + yield* session.removeMessage(ctx.params) + }).pipe(Effect.provide(SessionRunState.defaultLayer), Effect.provide(Session.defaultLayer)), + ), + ), + ) + return true + }) + + const deletePart = Effect.fn("SessionHttpApi.deletePart")(function* (ctx: { + params: { sessionID: SessionID; messageID: MessageID; partID: PartID } + }) { + const instance = yield* InstanceState.context + yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise(Session.Service.use((svc) => svc.removePart(ctx.params)).pipe(Effect.provide(Session.defaultLayer))), + ), + ) + return true + }) + + const updatePart = Effect.fn("SessionHttpApi.updatePart")(function* (ctx: { + params: { sessionID: SessionID; messageID: MessageID; partID: PartID } + payload: typeof MessageV2.Part.Type + }) { + const payload = MessageV2.Part.zod.parse(ctx.payload) + if ( + payload.id !== ctx.params.partID || + payload.messageID !== ctx.params.messageID || + payload.sessionID !== ctx.params.sessionID + ) { + throw new Error( + `Part mismatch: body.id='${payload.id}' vs partID='${ctx.params.partID}', body.messageID='${payload.messageID}' vs messageID='${ctx.params.messageID}', body.sessionID='${payload.sessionID}' vs sessionID='${ctx.params.sessionID}'`, + ) + } + const instance = yield* InstanceState.context + return yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise(Session.Service.use((svc) => svc.updatePart(payload)).pipe(Effect.provide(Session.defaultLayer))), + ), + ) + }) + return HttpApiBuilder.group(SessionApi, "session", (handlers) => handlers .handle("list", list) @@ -393,11 +533,17 @@ export const sessionHandlers = Layer.unwrap( .handle("remove", remove) .handle("update", update) .handle("fork", fork) - .handle("abort", abort), + .handle("abort", abort) + .handle("share", share) + .handle("unshare", unshare) + .handle("deleteMessage", deleteMessage) + .handle("deletePart", deletePart) + .handle("updatePart", updatePart), ) }), ).pipe( Layer.provide(Session.defaultLayer), + Layer.provide(SessionRunState.defaultLayer), Layer.provide(SessionStatus.defaultLayer), Layer.provide(Todo.defaultLayer), Layer.provide(SessionSummary.defaultLayer), diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index ba029f0a3..cbb46df22 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -107,6 +107,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.patch(SessionPaths.update, (c) => handler(c.req.raw, context)) app.post(SessionPaths.fork, (c) => handler(c.req.raw, context)) app.post(SessionPaths.abort, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.share, (c) => handler(c.req.raw, context)) + app.delete(SessionPaths.share, (c) => handler(c.req.raw, context)) + app.delete(SessionPaths.deleteMessage, (c) => handler(c.req.raw, context)) + app.delete(SessionPaths.deletePart, (c) => handler(c.req.raw, context)) + app.patch(SessionPaths.updatePart, (c) => handler(c.req.raw, context)) } return app diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index d589a45a0..7a1e8fd02 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -53,14 +53,14 @@ async function createTextMessage(directory: string, sessionID: SessionID, text: model: { providerID: ProviderID.make("test"), modelID: ModelID.make("test") }, time: { created: Date.now() }, }) - yield* svc.updatePart({ + const part = yield* svc.updatePart({ id: PartID.ascending(), sessionID, messageID: info.id, type: "text", text, }) - return info + return { info, part } }), ), }) @@ -123,11 +123,11 @@ describe("session HttpApi", () => { expect( await json( - await app().request(pathFor(SessionPaths.message, { sessionID: parent.id, messageID: message.id }), { + await app().request(pathFor(SessionPaths.message, { sessionID: parent.id, messageID: message.info.id }), { headers, }), ), - ).toMatchObject({ info: { id: message.id } }) + ).toMatchObject({ info: { id: message.info.id } }) }) test("serves lifecycle mutation routes through Hono bridge", async () => { @@ -173,4 +173,50 @@ describe("session HttpApi", () => { ), ).toBe(true) }) + + test("serves message mutation routes through Hono bridge", async () => { + await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) + const headers = { "x-opencode-directory": tmp.path, "content-type": "application/json" } + const session = await createSession(tmp.path, { title: "messages" }) + const first = await createTextMessage(tmp.path, session.id, "first") + const second = await createTextMessage(tmp.path, session.id, "second") + + const updated = await json( + await app().request( + pathFor(SessionPaths.updatePart, { + sessionID: session.id, + messageID: first.info.id, + partID: first.part.id, + }), + { + method: "PATCH", + headers, + body: JSON.stringify({ ...first.part, text: "updated" }), + }, + ), + ) + expect(updated).toMatchObject({ id: first.part.id, type: "text", text: "updated" }) + + expect( + await json( + await app().request( + pathFor(SessionPaths.deletePart, { + sessionID: session.id, + messageID: first.info.id, + partID: first.part.id, + }), + { method: "DELETE", headers }, + ), + ), + ).toBe(true) + + expect( + await json( + await app().request(pathFor(SessionPaths.deleteMessage, { sessionID: session.id, messageID: second.info.id }), { + method: "DELETE", + headers, + }), + ), + ).toBe(true) + }) }) From 301ecb185e06a230c6d720845b04effa84450976 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 16:01:07 +0000 Subject: [PATCH 215/426] chore: generate --- .../src/server/routes/instance/httpapi/session.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/session.ts b/packages/opencode/src/server/routes/instance/httpapi/session.ts index a028973d2..b38740d93 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/session.ts @@ -491,7 +491,9 @@ export const sessionHandlers = Layer.unwrap( const instance = yield* InstanceState.context yield* Effect.promise(() => Instance.restore(instance, () => - AppRuntime.runPromise(Session.Service.use((svc) => svc.removePart(ctx.params)).pipe(Effect.provide(Session.defaultLayer))), + AppRuntime.runPromise( + Session.Service.use((svc) => svc.removePart(ctx.params)).pipe(Effect.provide(Session.defaultLayer)), + ), ), ) return true @@ -514,7 +516,9 @@ export const sessionHandlers = Layer.unwrap( const instance = yield* InstanceState.context return yield* Effect.promise(() => Instance.restore(instance, () => - AppRuntime.runPromise(Session.Service.use((svc) => svc.updatePart(payload)).pipe(Effect.provide(Session.defaultLayer))), + AppRuntime.runPromise( + Session.Service.use((svc) => svc.updatePart(payload)).pipe(Effect.provide(Session.defaultLayer)), + ), ), ) }) From c5b67927afd2a86945fbcbce42d3cfc2eba047a8 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 26 Apr 2026 12:24:19 -0400 Subject: [PATCH 216/426] feat(httpapi): bridge remaining session routes (#24510) --- packages/opencode/specs/effect/http-api.md | 22 +- .../server/routes/instance/httpapi/session.ts | 366 +++++++++++++++++- .../src/server/routes/instance/index.ts | 9 + .../test/server/httpapi-session.test.ts | 60 ++- 4 files changed, 443 insertions(+), 14 deletions(-) diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 3ce4aa83a..5f16ef197 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -182,7 +182,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | `workspace` | `bridged` | adaptor/list/status/create/remove/session-restore | | top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose | | experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | -| `session` | `bridged` partial | read routes; lifecycle, message mutations, streaming remain | +| `session` | `bridged` | read, lifecycle, prompt, message/part mutations, revert, permission reply | | `sync` | `bridged` | start/replay/history | | `event` | `special` | SSE | | `pty` | `special` | websocket | @@ -294,25 +294,25 @@ This checklist tracks bridge parity only. Checked routes are available through t - [x] `POST /session` - create session. - [x] `DELETE /session/:sessionID` - delete session. - [x] `PATCH /session/:sessionID` - update session metadata. -- [ ] `POST /session/:sessionID/init` - run project init command. +- [x] `POST /session/:sessionID/init` - run project init command. - [x] `POST /session/:sessionID/fork` - fork session. - [x] `POST /session/:sessionID/abort` - abort session. - [x] `POST /session/:sessionID/share` - share session. - [x] `GET /session/:sessionID/diff` - session diff. - [x] `DELETE /session/:sessionID/share` - unshare session. -- [ ] `POST /session/:sessionID/summarize` - summarize session. +- [x] `POST /session/:sessionID/summarize` - summarize session. - [x] `GET /session/:sessionID/message` - list session messages. - [x] `GET /session/:sessionID/message/:messageID` - get message. - [x] `DELETE /session/:sessionID/message/:messageID` - delete message. - [x] `DELETE /session/:sessionID/message/:messageID/part/:partID` - delete part. - [x] `PATCH /session/:sessionID/message/:messageID/part/:partID` - update part. -- [ ] `POST /session/:sessionID/message` - prompt with streaming response. -- [ ] `POST /session/:sessionID/prompt_async` - async prompt. -- [ ] `POST /session/:sessionID/command` - run command. -- [ ] `POST /session/:sessionID/shell` - run shell command. -- [ ] `POST /session/:sessionID/revert` - revert message. -- [ ] `POST /session/:sessionID/unrevert` - restore reverted messages. -- [ ] `POST /session/:sessionID/permissions/:permissionID` - deprecated permission response route. +- [x] `POST /session/:sessionID/message` - prompt with streaming response. +- [x] `POST /session/:sessionID/prompt_async` - async prompt. +- [x] `POST /session/:sessionID/command` - run command. +- [x] `POST /session/:sessionID/shell` - run shell command. +- [x] `POST /session/:sessionID/revert` - revert message. +- [x] `POST /session/:sessionID/unrevert` - restore reverted messages. +- [x] `POST /session/:sessionID/permissions/:permissionID` - deprecated permission response route. ### Event Routes @@ -356,7 +356,7 @@ Prefer smaller PRs from here so route behavior and SDK/OpenAPI fallout stays rev 7. [x] Bridge sync start/replay/history routes. 8. [x] Bridge session read routes: list, status, get, children, todo, diff, messages. 9. [x] Bridge session lifecycle mutation routes: create, delete, update, fork, abort. -10. [ ] Bridge session share/summary/message/part mutation routes. +10. [x] Bridge remaining session mutation and prompt routes. 11. [ ] Replace event SSE with non-Hono Effect HTTP. 12. [ ] Replace pty websocket/control routes with non-Hono Effect HTTP. 13. [ ] Replace tui bridge routes or explicitly isolate them behind a non-Hono compatibility layer. diff --git a/packages/opencode/src/server/routes/instance/httpapi/session.ts b/packages/opencode/src/server/routes/instance/httpapi/session.ts index b38740d93..36645fd7e 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/session.ts @@ -1,22 +1,41 @@ import * as InstanceState from "@/effect/instance-state" import { AppRuntime } from "@/effect/app-runtime" +import { Agent } from "@/agent/agent" +import { Bus } from "@/bus" +import { Command } from "@/command" import { Permission } from "@/permission" +import { PermissionID } from "@/permission/schema" import { Instance } from "@/project/instance" +import { ModelID, ProviderID } from "@/provider/schema" import { SessionShare } from "@/share" import { Session } from "@/session" +import { SessionCompaction } from "@/session/compaction" import { MessageV2 } from "@/session/message-v2" import { SessionPrompt } from "@/session/prompt" +import { SessionRevert } from "@/session/revert" import { SessionRunState } from "@/session/run-state" import { SessionStatus } from "@/session/status" import { SessionSummary } from "@/session/summary" import { Todo } from "@/session/todo" import { MessageID, PartID, SessionID } from "@/session/schema" import { Snapshot } from "@/snapshot" +import { Log } from "@/util" +import { NamedError } from "@opencode-ai/core/util/error" import { Effect, Layer, Schema, Struct } from "effect" +import * as Stream from "effect/Stream" import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http" -import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { + HttpApi, + HttpApiBuilder, + HttpApiEndpoint, + HttpApiError, + HttpApiGroup, + HttpApiSchema, + OpenApi, +} from "effect/unstable/httpapi" import { Authorization } from "./auth" +const log = Log.create({ service: "server" }) const root = "/session" const ListQuery = Schema.Struct({ directory: Schema.optional(Schema.String), @@ -43,6 +62,31 @@ const UpdatePayload = Schema.Struct({ const ForkPayload = Schema.Struct(Struct.omit(Session.ForkInput.fields, ["sessionID"])).annotate({ identifier: "SessionForkInput", }) +const InitPayload = Schema.Struct({ + modelID: ModelID, + providerID: ProviderID, + messageID: MessageID, +}).annotate({ identifier: "SessionInitInput" }) +const SummarizePayload = Schema.Struct({ + providerID: ProviderID, + modelID: ModelID, + auto: Schema.optional(Schema.Boolean), +}).annotate({ identifier: "SessionSummarizeInput" }) +const PromptPayload = Schema.Struct(Struct.omit(SessionPrompt.PromptInput.fields, ["sessionID"])).annotate({ + identifier: "SessionPromptInput", +}) +const CommandPayload = Schema.Struct(Struct.omit(SessionPrompt.CommandInput.fields, ["sessionID"])).annotate({ + identifier: "SessionCommandInput", +}) +const ShellPayload = Schema.Struct(Struct.omit(SessionPrompt.ShellInput.fields, ["sessionID"])).annotate({ + identifier: "SessionShellInput", +}) +const RevertPayload = Schema.Struct(Struct.omit(SessionRevert.RevertInput.fields, ["sessionID"])).annotate({ + identifier: "SessionRevertInput", +}) +const PermissionResponsePayload = Schema.Struct({ + response: Permission.Reply, +}).annotate({ identifier: "SessionPermissionResponseInput" }) export const SessionPaths = { list: root, @@ -59,6 +103,15 @@ export const SessionPaths = { fork: `${root}/:sessionID/fork`, abort: `${root}/:sessionID/abort`, share: `${root}/:sessionID/share`, + init: `${root}/:sessionID/init`, + summarize: `${root}/:sessionID/summarize`, + prompt: `${root}/:sessionID/message`, + promptAsync: `${root}/:sessionID/prompt_async`, + command: `${root}/:sessionID/command`, + shell: `${root}/:sessionID/shell`, + revert: `${root}/:sessionID/revert`, + unrevert: `${root}/:sessionID/unrevert`, + permissions: `${root}/:sessionID/permissions/:permissionID`, deleteMessage: `${root}/:sessionID/message/:messageID`, deletePart: `${root}/:sessionID/message/:messageID/part/:partID`, updatePart: `${root}/:sessionID/message/:messageID/part/:partID`, @@ -201,6 +254,18 @@ export const SessionApi = HttpApi.make("session") description: "Abort an active session and stop any ongoing AI processing or command execution.", }), ), + HttpApiEndpoint.post("init", SessionPaths.init, { + params: { sessionID: SessionID }, + payload: InitPayload, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.init", + summary: "Initialize session", + description: + "Analyze the current application and create an AGENTS.md file with project-specific agent configurations.", + }), + ), HttpApiEndpoint.post("share", SessionPaths.share, { params: { sessionID: SessionID }, success: Session.Info, @@ -221,6 +286,95 @@ export const SessionApi = HttpApi.make("session") description: "Remove the shareable link for a session, making it private again.", }), ), + HttpApiEndpoint.post("summarize", SessionPaths.summarize, { + params: { sessionID: SessionID }, + payload: SummarizePayload, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.summarize", + summary: "Summarize session", + description: "Generate a concise summary of the session using AI compaction to preserve key information.", + }), + ), + HttpApiEndpoint.post("prompt", SessionPaths.prompt, { + params: { sessionID: SessionID }, + payload: PromptPayload, + success: MessageV2.WithParts, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.prompt", + summary: "Send message", + description: "Create and send a new message to a session, streaming the AI response.", + }), + ), + HttpApiEndpoint.post("promptAsync", SessionPaths.promptAsync, { + params: { sessionID: SessionID }, + payload: PromptPayload, + success: HttpApiSchema.NoContent, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.prompt_async", + summary: "Send async message", + description: + "Create and send a new message to a session asynchronously, starting the session if needed and returning immediately.", + }), + ), + HttpApiEndpoint.post("command", SessionPaths.command, { + params: { sessionID: SessionID }, + payload: CommandPayload, + success: MessageV2.WithParts, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.command", + summary: "Send command", + description: "Send a new command to a session for execution by the AI assistant.", + }), + ), + HttpApiEndpoint.post("shell", SessionPaths.shell, { + params: { sessionID: SessionID }, + payload: ShellPayload, + success: MessageV2.WithParts, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.shell", + summary: "Run shell command", + description: "Execute a shell command within the session context and return the AI's response.", + }), + ), + HttpApiEndpoint.post("revert", SessionPaths.revert, { + params: { sessionID: SessionID }, + payload: RevertPayload, + success: Session.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.revert", + summary: "Revert message", + description: "Revert a specific message in a session, undoing its effects and restoring the previous state.", + }), + ), + HttpApiEndpoint.post("unrevert", SessionPaths.unrevert, { + params: { sessionID: SessionID }, + success: Session.Info, + }).annotateMerge( + OpenApi.annotations({ + identifier: "session.unrevert", + summary: "Restore reverted messages", + description: "Restore all previously reverted messages in a session.", + }), + ), + HttpApiEndpoint.post("permissionRespond", SessionPaths.permissions, { + params: { sessionID: SessionID, permissionID: PermissionID }, + payload: PermissionResponsePayload, + success: Schema.Boolean, + }).annotateMerge( + OpenApi.annotations({ + identifier: "permission.respond", + summary: "Respond to permission", + description: "Approve or deny a permission request from the AI assistant.", + deprecated: true, + }), + ), HttpApiEndpoint.delete("deleteMessage", SessionPaths.deleteMessage, { params: { sessionID: SessionID, messageID: MessageID }, success: Schema.Boolean, @@ -317,6 +471,14 @@ export const sessionHandlers = Layer.unwrap( params: { sessionID: SessionID } query: typeof MessagesQuery.Type }) { + if (ctx.query.before !== undefined && ctx.query.limit === undefined) return yield* new HttpApiError.BadRequest({}) + if (ctx.query.before !== undefined) { + const before = ctx.query.before + yield* Effect.try({ + try: () => MessageV2.cursor.decode(before), + catch: () => new HttpApiError.BadRequest({}), + }) + } if (ctx.query.limit === undefined || ctx.query.limit === 0) { yield* session.get(ctx.params.sessionID) return yield* session.messages({ sessionID: ctx.params.sessionID }) @@ -434,6 +596,29 @@ export const sessionHandlers = Layer.unwrap( return true }) + const init = Effect.fn("SessionHttpApi.init")(function* (ctx: { + params: { sessionID: SessionID } + payload: typeof InitPayload.Type + }) { + const instance = yield* InstanceState.context + yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + SessionPrompt.Service.use((svc) => + svc.command({ + sessionID: ctx.params.sessionID, + messageID: ctx.payload.messageID, + model: `${ctx.payload.providerID}/${ctx.payload.modelID}`, + command: Command.Default.INIT, + arguments: "", + }), + ).pipe(Effect.provide(SessionPrompt.defaultLayer)), + ), + ), + ) + return true + }) + const share = Effect.fn("SessionHttpApi.share")(function* (ctx: { params: { sessionID: SessionID } }) { const instance = yield* InstanceState.context return yield* Effect.promise(() => @@ -466,6 +651,174 @@ export const sessionHandlers = Layer.unwrap( ) }) + const summarize = Effect.fn("SessionHttpApi.summarize")(function* (ctx: { + params: { sessionID: SessionID } + payload: typeof SummarizePayload.Type + }) { + const instance = yield* InstanceState.context + yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + Effect.gen(function* () { + const session = yield* Session.Service + const revert = yield* SessionRevert.Service + const compact = yield* SessionCompaction.Service + const prompt = yield* SessionPrompt.Service + const agent = yield* Agent.Service + + yield* revert.cleanup(yield* session.get(ctx.params.sessionID)) + const messages = yield* session.messages({ sessionID: ctx.params.sessionID }) + const defaultAgent = yield* agent.defaultAgent() + const currentAgent = messages.findLast((message) => message.info.role === "user")?.info.agent ?? defaultAgent + + yield* compact.create({ + sessionID: ctx.params.sessionID, + agent: currentAgent, + model: { + providerID: ctx.payload.providerID, + modelID: ctx.payload.modelID, + }, + auto: ctx.payload.auto ?? false, + }) + yield* prompt.loop({ sessionID: ctx.params.sessionID }) + }).pipe( + Effect.provide(SessionRevert.defaultLayer), + Effect.provide(SessionCompaction.defaultLayer), + Effect.provide(SessionPrompt.defaultLayer), + Effect.provide(Agent.defaultLayer), + Effect.provide(Session.defaultLayer), + ), + ), + ), + ) + return true + }) + + const prompt = Effect.fn("SessionHttpApi.prompt")(function* (ctx: { + params: { sessionID: SessionID } + payload: typeof PromptPayload.Type + }) { + const instance = yield* InstanceState.context + return HttpServerResponse.stream( + Stream.fromEffect( + Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + SessionPrompt.Service.use((svc) => + svc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID } as unknown as SessionPrompt.PromptInput), + ).pipe(Effect.provide(SessionPrompt.defaultLayer)), + ), + ), + ), + ).pipe(Stream.map((message) => JSON.stringify(message)), Stream.encodeText), + { contentType: "application/json" }, + ) + }) + + const promptAsync = Effect.fn("SessionHttpApi.promptAsync")(function* (ctx: { + params: { sessionID: SessionID } + payload: typeof PromptPayload.Type + }) { + const instance = yield* InstanceState.context + yield* Effect.sync(() => { + Instance.restore(instance, () => { + void AppRuntime.runPromise( + SessionPrompt.Service.use((svc) => + svc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID } as unknown as SessionPrompt.PromptInput), + ).pipe(Effect.provide(SessionPrompt.defaultLayer)), + ).catch((error) => { + log.error("prompt_async failed", { sessionID: ctx.params.sessionID, error }) + void Bus.publish(Session.Event.Error, { + sessionID: ctx.params.sessionID, + error: new NamedError.Unknown({ + message: error instanceof Error ? error.message : String(error), + }).toObject(), + }) + }) + }) + }) + return HttpApiSchema.NoContent.make() + }) + + const command = Effect.fn("SessionHttpApi.command")(function* (ctx: { + params: { sessionID: SessionID } + payload: typeof CommandPayload.Type + }) { + const instance = yield* InstanceState.context + return yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + SessionPrompt.Service.use((svc) => + svc.command({ ...ctx.payload, sessionID: ctx.params.sessionID } as SessionPrompt.CommandInput), + ).pipe(Effect.provide(SessionPrompt.defaultLayer)), + ), + ), + ) + }) + + const shell = Effect.fn("SessionHttpApi.shell")(function* (ctx: { + params: { sessionID: SessionID } + payload: typeof ShellPayload.Type + }) { + const instance = yield* InstanceState.context + return yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + SessionPrompt.Service.use((svc) => + svc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID } as SessionPrompt.ShellInput), + ).pipe(Effect.provide(SessionPrompt.defaultLayer)), + ), + ), + ) + }) + + const revert = Effect.fn("SessionHttpApi.revert")(function* (ctx: { + params: { sessionID: SessionID } + payload: typeof RevertPayload.Type + }) { + const instance = yield* InstanceState.context + log.info("revert", ctx.payload) + return yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + SessionRevert.Service.use((svc) => + svc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload }), + ).pipe(Effect.provide(SessionRevert.defaultLayer)), + ), + ), + ) + }) + + const unrevert = Effect.fn("SessionHttpApi.unrevert")(function* (ctx: { params: { sessionID: SessionID } }) { + const instance = yield* InstanceState.context + return yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + SessionRevert.Service.use((svc) => svc.unrevert({ sessionID: ctx.params.sessionID })).pipe( + Effect.provide(SessionRevert.defaultLayer), + ), + ), + ), + ) + }) + + const permissionRespond = Effect.fn("SessionHttpApi.permissionRespond")(function* (ctx: { + params: { permissionID: PermissionID } + payload: typeof PermissionResponsePayload.Type + }) { + const instance = yield* InstanceState.context + yield* Effect.promise(() => + Instance.restore(instance, () => + AppRuntime.runPromise( + Permission.Service.use((svc) => + svc.reply({ requestID: ctx.params.permissionID, reply: ctx.payload.response }), + ).pipe(Effect.provide(Permission.defaultLayer)), + ), + ), + ) + return true + }) + const deleteMessage = Effect.fn("SessionHttpApi.deleteMessage")(function* (ctx: { params: { sessionID: SessionID; messageID: MessageID } }) { @@ -503,7 +856,7 @@ export const sessionHandlers = Layer.unwrap( params: { sessionID: SessionID; messageID: MessageID; partID: PartID } payload: typeof MessageV2.Part.Type }) { - const payload = MessageV2.Part.zod.parse(ctx.payload) + const payload = ctx.payload as MessageV2.Part if ( payload.id !== ctx.params.partID || payload.messageID !== ctx.params.messageID || @@ -538,8 +891,17 @@ export const sessionHandlers = Layer.unwrap( .handle("update", update) .handle("fork", fork) .handle("abort", abort) + .handle("init", init) .handle("share", share) .handle("unshare", unshare) + .handle("summarize", summarize) + .handle("prompt", prompt) + .handle("promptAsync", promptAsync) + .handle("command", command) + .handle("shell", shell) + .handle("revert", revert) + .handle("unrevert", unrevert) + .handle("permissionRespond", permissionRespond) .handle("deleteMessage", deleteMessage) .handle("deletePart", deletePart) .handle("updatePart", updatePart), diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index cbb46df22..4c0503af5 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -105,10 +105,19 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { app.post(SessionPaths.create, (c) => handler(c.req.raw, context)) app.delete(SessionPaths.remove, (c) => handler(c.req.raw, context)) app.patch(SessionPaths.update, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.init, (c) => handler(c.req.raw, context)) app.post(SessionPaths.fork, (c) => handler(c.req.raw, context)) app.post(SessionPaths.abort, (c) => handler(c.req.raw, context)) app.post(SessionPaths.share, (c) => handler(c.req.raw, context)) app.delete(SessionPaths.share, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.summarize, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.prompt, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.promptAsync, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.command, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.shell, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.revert, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.unrevert, (c) => handler(c.req.raw, context)) + app.post(SessionPaths.permissions, (c) => handler(c.req.raw, context)) app.delete(SessionPaths.deleteMessage, (c) => handler(c.req.raw, context)) app.delete(SessionPaths.deletePart, (c) => handler(c.req.raw, context)) app.patch(SessionPaths.updatePart, (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index 7a1e8fd02..e6c091982 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test" import type { UpgradeWebSocket } from "hono/ws" import { Effect } from "effect" import { Flag } from "@opencode-ai/core/flag/flag" +import { PermissionID } from "../../src/permission/schema" import { ModelID, ProviderID } from "../../src/provider/schema" import { Instance } from "../../src/project/instance" import { InstanceRoutes } from "../../src/server/routes/instance" @@ -118,9 +119,25 @@ describe("session HttpApi", () => { headers, }) const messagePage = await json(messages) - expect(messages.headers.get("x-next-cursor")).toBeTruthy() + const nextCursor = messages.headers.get("x-next-cursor") + expect(nextCursor).toBeTruthy() expect(messagePage[0]?.parts[0]).toMatchObject({ type: "text" }) + expect( + ( + await app().request(`${pathFor(SessionPaths.messages, { sessionID: parent.id })}?before=${nextCursor}`, { + headers, + }) + ).status, + ).toBe(400) + expect( + ( + await app().request(`${pathFor(SessionPaths.messages, { sessionID: parent.id })}?limit=1&before=invalid`, { + headers, + }) + ).status, + ).toBe(400) + expect( await json( await app().request(pathFor(SessionPaths.message, { sessionID: parent.id, messageID: message.info.id }), { @@ -219,4 +236,45 @@ describe("session HttpApi", () => { ), ).toBe(true) }) + + test("serves remaining non-LLM session mutation routes through Hono bridge", async () => { + await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) + const headers = { "x-opencode-directory": tmp.path, "content-type": "application/json" } + const session = await createSession(tmp.path, { title: "remaining" }) + + expect( + await json( + await app().request(pathFor(SessionPaths.revert, { sessionID: session.id }), { + method: "POST", + headers, + body: JSON.stringify({ messageID: MessageID.ascending() }), + }), + ), + ).toMatchObject({ id: session.id }) + + expect( + await json( + await app().request(pathFor(SessionPaths.unrevert, { sessionID: session.id }), { + method: "POST", + headers, + }), + ), + ).toMatchObject({ id: session.id }) + + expect( + await json( + await app().request( + pathFor(SessionPaths.permissions, { + sessionID: session.id, + permissionID: String(PermissionID.ascending()), + }), + { + method: "POST", + headers, + body: JSON.stringify({ response: "once" }), + }, + ), + ), + ).toBe(true) + }) }) From 41f5e8a8616b22862f181c67f30c5420ac2e47af Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 16:25:19 +0000 Subject: [PATCH 217/426] chore: generate --- .../server/routes/instance/httpapi/session.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/session.ts b/packages/opencode/src/server/routes/instance/httpapi/session.ts index 36645fd7e..08bf424f4 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/session.ts @@ -350,7 +350,8 @@ export const SessionApi = HttpApi.make("session") OpenApi.annotations({ identifier: "session.revert", summary: "Revert message", - description: "Revert a specific message in a session, undoing its effects and restoring the previous state.", + description: + "Revert a specific message in a session, undoing its effects and restoring the previous state.", }), ), HttpApiEndpoint.post("unrevert", SessionPaths.unrevert, { @@ -669,7 +670,8 @@ export const sessionHandlers = Layer.unwrap( yield* revert.cleanup(yield* session.get(ctx.params.sessionID)) const messages = yield* session.messages({ sessionID: ctx.params.sessionID }) const defaultAgent = yield* agent.defaultAgent() - const currentAgent = messages.findLast((message) => message.info.role === "user")?.info.agent ?? defaultAgent + const currentAgent = + messages.findLast((message) => message.info.role === "user")?.info.agent ?? defaultAgent yield* compact.create({ sessionID: ctx.params.sessionID, @@ -705,12 +707,18 @@ export const sessionHandlers = Layer.unwrap( Instance.restore(instance, () => AppRuntime.runPromise( SessionPrompt.Service.use((svc) => - svc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID } as unknown as SessionPrompt.PromptInput), + svc.prompt({ + ...ctx.payload, + sessionID: ctx.params.sessionID, + } as unknown as SessionPrompt.PromptInput), ).pipe(Effect.provide(SessionPrompt.defaultLayer)), ), ), ), - ).pipe(Stream.map((message) => JSON.stringify(message)), Stream.encodeText), + ).pipe( + Stream.map((message) => JSON.stringify(message)), + Stream.encodeText, + ), { contentType: "application/json" }, ) }) @@ -781,9 +789,9 @@ export const sessionHandlers = Layer.unwrap( return yield* Effect.promise(() => Instance.restore(instance, () => AppRuntime.runPromise( - SessionRevert.Service.use((svc) => - svc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload }), - ).pipe(Effect.provide(SessionRevert.defaultLayer)), + SessionRevert.Service.use((svc) => svc.revert({ sessionID: ctx.params.sessionID, ...ctx.payload })).pipe( + Effect.provide(SessionRevert.defaultLayer), + ), ), ), ) From 79c66e353f718684f6e9da01c2aa2444cebcaf53 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 26 Apr 2026 12:50:01 -0400 Subject: [PATCH 218/426] sync --- .opencode/agent/translator.md | 899 -------------------- .opencode/command/translate.md | 14 + packages/web/src/content/docs/ar/zen.mdx | 27 +- packages/web/src/content/docs/bs/zen.mdx | 27 +- packages/web/src/content/docs/da/zen.mdx | 27 +- packages/web/src/content/docs/de/zen.mdx | 27 +- packages/web/src/content/docs/es/zen.mdx | 27 +- packages/web/src/content/docs/fr/zen.mdx | 27 +- packages/web/src/content/docs/it/zen.mdx | 27 +- packages/web/src/content/docs/ja/zen.mdx | 27 +- packages/web/src/content/docs/ko/zen.mdx | 27 +- packages/web/src/content/docs/nb/zen.mdx | 27 +- packages/web/src/content/docs/pl/zen.mdx | 29 +- packages/web/src/content/docs/pt-br/zen.mdx | 27 +- packages/web/src/content/docs/ru/zen.mdx | 27 +- packages/web/src/content/docs/th/zen.mdx | 29 +- packages/web/src/content/docs/tr/zen.mdx | 27 +- packages/web/src/content/docs/zen.mdx | 27 +- packages/web/src/content/docs/zh-cn/zen.mdx | 27 +- packages/web/src/content/docs/zh-tw/zen.mdx | 27 +- 20 files changed, 322 insertions(+), 1081 deletions(-) delete mode 100644 .opencode/agent/translator.md create mode 100644 .opencode/command/translate.md diff --git a/.opencode/agent/translator.md b/.opencode/agent/translator.md deleted file mode 100644 index 8ac7025f1..000000000 --- a/.opencode/agent/translator.md +++ /dev/null @@ -1,899 +0,0 @@ ---- -description: Translate content for a specified locale while preserving technical terms -mode: subagent -model: opencode/gpt-5.4 ---- - -You are a professional translator and localization specialist. - -Translate the user's content into the requested target locale (language + region, e.g. fr-FR, de-DE). - -Requirements: - -- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure). -- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks. -- Also preserve every term listed in the Do-Not-Translate glossary below. -- Also apply locale-specific guidance from `.opencode/glossary/.md` when available (for example, `zh-cn.md`). -- Do not modify fenced code blocks. -- Output ONLY the translation (no commentary). - -If the target locale is missing, ask the user to provide it. -If no locale-specific glossary exists, use the global glossary only. - ---- - -# Locale-Specific Glossaries - -When a locale glossary exists, use it to: - -- Apply preferred wording for recurring UI/docs terms in that locale -- Preserve locale-specific do-not-translate terms and casing decisions -- Prefer natural phrasing over literal translation when the locale file calls it out -- If the repo uses a locale alias slug, apply that file too (for example, `pt-BR` maps to `br.md` in this repo) - -Locale guidance does not override code/command preservation rules or the global Do-Not-Translate glossary below. - ---- - -# Do-Not-Translate Terms (OpenCode Docs) - -Generated from: `packages/web/src/content/docs/*.mdx` (default English docs) -Generated on: 2026-02-10 - -Use this as a translation QA checklist / glossary. Preserve listed terms exactly (spelling, casing, punctuation). - -General rules (verbatim, even if not listed below): - -- Anything inside inline code (single backticks) or fenced code blocks (triple backticks) -- MDX/JS code in docs: `import ... from "..."`, component tags, identifiers -- CLI commands, flags, config keys/values, file paths, URLs/domains, and env vars - -## Proper nouns and product names - -Additional (not reliably captured via link text): - -```text -Astro -Bun -Chocolatey -Cursor -Docker -Git -GitHub Actions -GitLab CI -GNOME Terminal -Homebrew -Mise -Neovim -Node.js -npm -Obsidian -opencode -opencode-ai -Paru -pnpm -ripgrep -Scoop -SST -Starlight -Visual Studio Code -VS Code -VSCodium -Windsurf -Windows Terminal -Yarn -Zellij -Zed -anomalyco -``` - -Extracted from link labels in the English docs (review and prune as desired): - -```text -@openspoon/subtask2 -302.AI console -ACP progress report -Agent Client Protocol -Agent Skills -Agentic -AGENTS.md -AI SDK -Alacritty -Anthropic -Anthropic's Data Policies -Atom One -Avante.nvim -Ayu -Azure AI Foundry -Azure portal -Baseten -built-in GITHUB_TOKEN -Bun.$ -Catppuccin -Cerebras console -ChatGPT Plus or Pro -Cloudflare dashboard -CodeCompanion.nvim -CodeNomad -Configuring Adapters: Environment Variables -Context7 MCP server -Cortecs console -Deep Infra dashboard -DeepSeek console -Duo Agent Platform -Everforest -Fireworks AI console -Firmware dashboard -Ghostty -GitLab CLI agents docs -GitLab docs -GitLab User Settings > Access Tokens -Granular Rules (Object Syntax) -Grep by Vercel -Groq console -Gruvbox -Helicone -Helicone documentation -Helicone Header Directory -Helicone's Model Directory -Hugging Face Inference Providers -Hugging Face settings -install WSL -IO.NET console -JetBrains IDE -Kanagawa -Kitty -MiniMax API Console -Models.dev -Moonshot AI console -Nebius Token Factory console -Nord -OAuth -Ollama integration docs -OpenAI's Data Policies -OpenChamber -OpenCode -OpenCode config -OpenCode Config -OpenCode TUI with the opencode theme -OpenCode Web - Active Session -OpenCode Web - New Session -OpenCode Web - See Servers -OpenCode Zen -OpenCode-Obsidian -OpenRouter dashboard -OpenWork -OVHcloud panel -Pro+ subscription -SAP BTP Cockpit -Scaleway Console IAM settings -Scaleway Generative APIs -SDK documentation -Sentry MCP server -shell API -Together AI console -Tokyonight -Unified Billing -Venice AI console -Vercel dashboard -WezTerm -Windows Subsystem for Linux (WSL) -WSL -WSL (Windows Subsystem for Linux) -WSL extension -xAI console -Z.AI API console -Zed -ZenMux dashboard -Zod -``` - -## Acronyms and initialisms - -```text -ACP -AGENTS -AI -AI21 -ANSI -API -AST -AWS -BTP -CD -CDN -CI -CLI -CMD -CORS -DEBUG -EKS -ERROR -FAQ -GLM -GNOME -GPT -HTML -HTTP -HTTPS -IAM -ID -IDE -INFO -IO -IP -IRSA -JS -JSON -JSONC -K2 -LLM -LM -LSP -M2 -MCP -MR -NET -NPM -NTLM -OIDC -OS -PAT -PATH -PHP -PR -PTY -README -RFC -RPC -SAP -SDK -SKILL -SSE -SSO -TS -TTY -TUI -UI -URL -US -UX -VCS -VPC -VPN -VS -WARN -WSL -X11 -YAML -``` - -## Code identifiers used in prose (CamelCase, mixedCase) - -```text -apiKey -AppleScript -AssistantMessage -baseURL -BurntSushi -ChatGPT -ClangFormat -CodeCompanion -CodeNomad -DeepSeek -DefaultV2 -FileContent -FileDiff -FileNode -fineGrained -FormatterStatus -GitHub -GitLab -iTerm2 -JavaScript -JetBrains -macOS -mDNS -MiniMax -NeuralNomadsAI -NickvanDyke -NoeFabris -OpenAI -OpenAPI -OpenChamber -OpenCode -OpenRouter -OpenTUI -OpenWork -ownUserPermissions -PowerShell -ProviderAuthAuthorization -ProviderAuthMethod -ProviderInitError -SessionStatus -TabItem -tokenType -ToolIDs -ToolList -TypeScript -typesUrl -UserMessage -VcsInfo -WebView2 -WezTerm -xAI -ZenMux -``` - -## OpenCode CLI commands (as shown in docs) - -```text -opencode -opencode [project] -opencode /path/to/project -opencode acp -opencode agent [command] -opencode agent create -opencode agent list -opencode attach [url] -opencode attach http://10.20.30.40:4096 -opencode attach http://localhost:4096 -opencode auth [command] -opencode auth list -opencode auth login -opencode auth logout -opencode auth ls -opencode export [sessionID] -opencode github [command] -opencode github install -opencode github run -opencode import -opencode import https://opncd.ai/s/abc123 -opencode import session.json -opencode mcp [command] -opencode mcp add -opencode mcp auth [name] -opencode mcp auth list -opencode mcp auth ls -opencode mcp auth my-oauth-server -opencode mcp auth sentry -opencode mcp debug -opencode mcp debug my-oauth-server -opencode mcp list -opencode mcp logout [name] -opencode mcp logout my-oauth-server -opencode mcp ls -opencode models --refresh -opencode models [provider] -opencode models anthropic -opencode run [message..] -opencode run Explain the use of context in Go -opencode serve -opencode serve --cors http://localhost:5173 --cors https://app.example.com -opencode serve --hostname 0.0.0.0 --port 4096 -opencode serve [--port ] [--hostname ] [--cors ] -opencode session [command] -opencode session list -opencode session delete -opencode stats -opencode uninstall -opencode upgrade -opencode upgrade [target] -opencode upgrade v0.1.48 -opencode web -opencode web --cors https://example.com -opencode web --hostname 0.0.0.0 -opencode web --mdns -opencode web --mdns --mdns-domain myproject.local -opencode web --port 4096 -opencode web --port 4096 --hostname 0.0.0.0 -opencode.server.close() -``` - -## Slash commands and routes - -```text -/agent -/auth/:id -/clear -/command -/config -/config/providers -/connect -/continue -/doc -/editor -/event -/experimental/tool?provider=

    &model= -/experimental/tool/ids -/export -/file?path= -/file/content?path=

    -/file/status -/find?pattern= -/find/file -/find/file?query= -/find/symbol?query= -/formatter -/global/event -/global/health -/help -/init -/instance/dispose -/log -/lsp -/mcp -/mnt/ -/mnt/c/ -/mnt/d/ -/models -/oc -/opencode -/path -/project -/project/current -/provider -/provider/{id}/oauth/authorize -/provider/{id}/oauth/callback -/provider/auth -/q -/quit -/redo -/resume -/session -/session/:id -/session/:id/abort -/session/:id/children -/session/:id/command -/session/:id/diff -/session/:id/fork -/session/:id/init -/session/:id/message -/session/:id/message/:messageID -/session/:id/permissions/:permissionID -/session/:id/prompt_async -/session/:id/revert -/session/:id/share -/session/:id/shell -/session/:id/summarize -/session/:id/todo -/session/:id/unrevert -/session/status -/share -/summarize -/theme -/tui -/tui/append-prompt -/tui/clear-prompt -/tui/control/next -/tui/control/response -/tui/execute-command -/tui/open-help -/tui/open-models -/tui/open-sessions -/tui/open-themes -/tui/show-toast -/tui/submit-prompt -/undo -/Users/username -/Users/username/projects/* -/vcs -``` - -## CLI flags and short options - -```text ---agent ---attach ---command ---continue ---cors ---cwd ---days ---dir ---dry-run ---event ---file ---force ---fork ---format ---help ---hostname ---hostname 0.0.0.0 ---keep-config ---keep-data ---log-level ---max-count ---mdns ---mdns-domain ---method ---model ---models ---port ---print-logs ---project ---prompt ---refresh ---session ---share ---title ---token ---tools ---verbose ---version ---wait - --c --d --f --h --m --n --s --v -``` - -## Environment variables - -```text -AI_API_URL -AI_FLOW_CONTEXT -AI_FLOW_EVENT -AI_FLOW_INPUT -AICORE_DEPLOYMENT_ID -AICORE_RESOURCE_GROUP -AICORE_SERVICE_KEY -ANTHROPIC_API_KEY -AWS_ACCESS_KEY_ID -AWS_BEARER_TOKEN_BEDROCK -AWS_PROFILE -AWS_REGION -AWS_ROLE_ARN -AWS_SECRET_ACCESS_KEY -AWS_WEB_IDENTITY_TOKEN_FILE -AZURE_COGNITIVE_SERVICES_RESOURCE_NAME -AZURE_RESOURCE_NAME -CI_PROJECT_DIR -CI_SERVER_FQDN -CI_WORKLOAD_REF -CLOUDFLARE_ACCOUNT_ID -CLOUDFLARE_API_TOKEN -CLOUDFLARE_GATEWAY_ID -CONTEXT7_API_KEY -GITHUB_TOKEN -GITLAB_AI_GATEWAY_URL -GITLAB_HOST -GITLAB_INSTANCE_URL -GITLAB_OAUTH_CLIENT_ID -GITLAB_TOKEN -GITLAB_TOKEN_OPENCODE -GOOGLE_APPLICATION_CREDENTIALS -GOOGLE_CLOUD_PROJECT -HTTP_PROXY -HTTPS_PROXY -K2_ -MY_API_KEY -MY_ENV_VAR -MY_MCP_CLIENT_ID -MY_MCP_CLIENT_SECRET -NO_PROXY -NODE_ENV -NODE_EXTRA_CA_CERTS -NPM_AUTH_TOKEN -OC_ALLOW_WAYLAND -OPENCODE_API_KEY -OPENCODE_AUTH_JSON -OPENCODE_AUTO_SHARE -OPENCODE_CLIENT -OPENCODE_CONFIG -OPENCODE_CONFIG_CONTENT -OPENCODE_CONFIG_DIR -OPENCODE_DISABLE_AUTOCOMPACT -OPENCODE_DISABLE_AUTOUPDATE -OPENCODE_DISABLE_CLAUDE_CODE -OPENCODE_DISABLE_CLAUDE_CODE_PROMPT -OPENCODE_DISABLE_CLAUDE_CODE_SKILLS -OPENCODE_DISABLE_DEFAULT_PLUGINS -OPENCODE_DISABLE_LSP_DOWNLOAD -OPENCODE_DISABLE_MODELS_FETCH -OPENCODE_DISABLE_PRUNE -OPENCODE_DISABLE_TERMINAL_TITLE -OPENCODE_ENABLE_EXA -OPENCODE_ENABLE_EXPERIMENTAL_MODELS -OPENCODE_EXPERIMENTAL -OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS -OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT -OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER -OPENCODE_EXPERIMENTAL_EXA -OPENCODE_EXPERIMENTAL_FILEWATCHER -OPENCODE_EXPERIMENTAL_ICON_DISCOVERY -OPENCODE_EXPERIMENTAL_LSP_TOOL -OPENCODE_EXPERIMENTAL_LSP_TY -OPENCODE_EXPERIMENTAL_MARKDOWN -OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX -OPENCODE_EXPERIMENTAL_OXFMT -OPENCODE_EXPERIMENTAL_PLAN_MODE -OPENCODE_ENABLE_QUESTION_TOOL -OPENCODE_FAKE_VCS -OPENCODE_GIT_BASH_PATH -OPENCODE_MODEL -OPENCODE_MODELS_URL -OPENCODE_PERMISSION -OPENCODE_PORT -OPENCODE_SERVER_PASSWORD -OPENCODE_SERVER_USERNAME -PROJECT_ROOT -RESOURCE_NAME -RUST_LOG -VARIABLE_NAME -VERTEX_LOCATION -XDG_CONFIG_HOME -``` - -## Package/module identifiers - -```text -../../../config.mjs -@astrojs/starlight/components -@opencode-ai/plugin -@opencode-ai/sdk -path -shescape -zod - -@ -@ai-sdk/anthropic -@ai-sdk/cerebras -@ai-sdk/google -@ai-sdk/openai -@ai-sdk/openai-compatible -@File#L37-42 -@modelcontextprotocol/server-everything -@opencode -``` - -## GitHub owner/repo slugs referenced in docs - -```text -24601/opencode-zellij-namer -angristan/opencode-wakatime -anomalyco/opencode -apps/opencode-agent -athal7/opencode-devcontainers -awesome-opencode/awesome-opencode -backnotprop/plannotator -ben-vargas/ai-sdk-provider-opencode-sdk -btriapitsyn/openchamber -BurntSushi/ripgrep -Cluster444/agentic -code-yeongyu/oh-my-opencode -darrenhinde/opencode-agents -different-ai/opencode-scheduler -different-ai/openwork -features/copilot -folke/tokyonight.nvim -franlol/opencode-md-table-formatter -ggml-org/llama.cpp -ghoulr/opencode-websearch-cited.git -H2Shami/opencode-helicone-session -hosenur/portal -jamesmurdza/daytona -jenslys/opencode-gemini-auth -JRedeker/opencode-morph-fast-apply -JRedeker/opencode-shell-strategy -kdcokenny/ocx -kdcokenny/opencode-background-agents -kdcokenny/opencode-notify -kdcokenny/opencode-workspace -kdcokenny/opencode-worktree -login/device -mohak34/opencode-notifier -morhetz/gruvbox -mtymek/opencode-obsidian -NeuralNomadsAI/CodeNomad -nick-vi/opencode-type-inject -NickvanDyke/opencode.nvim -NoeFabris/opencode-antigravity-auth -nordtheme/nord -numman-ali/opencode-openai-codex-auth -olimorris/codecompanion.nvim -panta82/opencode-notificator -rebelot/kanagawa.nvim -remorses/kimaki -sainnhe/everforest -shekohex/opencode-google-antigravity-auth -shekohex/opencode-pty.git -spoons-and-mirrors/subtask2 -sudo-tee/opencode.nvim -supermemoryai/opencode-supermemory -Tarquinen/opencode-dynamic-context-pruning -Th3Whit3Wolf/one-nvim -upstash/context7 -vtemian/micode -vtemian/octto -yetone/avante.nvim -zenobi-us/opencode-plugin-template -zenobi-us/opencode-skillful -``` - -## Paths, filenames, globs, and URLs - -```text -./.opencode/themes/*.json -.//storage/ -./config/#custom-directory -./global/storage/ -.agents/skills/*/SKILL.md -.agents/skills//SKILL.md -.clang-format -.claude -.claude/skills -.claude/skills/*/SKILL.md -.claude/skills//SKILL.md -.env -.github/workflows/opencode.yml -.gitignore -.gitlab-ci.yml -.ignore -.NET SDK -.npmrc -.ocamlformat -.opencode -.opencode/ -.opencode/agents/ -.opencode/commands/ -.opencode/commands/test.md -.opencode/modes/ -.opencode/plans/*.md -.opencode/plugins/ -.opencode/skills//SKILL.md -.opencode/skills/git-release/SKILL.md -.opencode/tools/ -.well-known/opencode -{ type: "raw" \| "patch", content: string } -{file:path/to/file} -**/*.js -%USERPROFILE%/intelephense/license.txt -%USERPROFILE%\.cache\opencode -%USERPROFILE%\.config\opencode\opencode.jsonc -%USERPROFILE%\.config\opencode\plugins -%USERPROFILE%\.local\share\opencode -%USERPROFILE%\.local\share\opencode\log -/.opencode/themes/*.json -/ -/.opencode/plugins/ -~ -~/... -~/.agents/skills/*/SKILL.md -~/.agents/skills//SKILL.md -~/.aws/credentials -~/.bashrc -~/.cache/opencode -~/.cache/opencode/node_modules/ -~/.claude/CLAUDE.md -~/.claude/skills/ -~/.claude/skills/*/SKILL.md -~/.claude/skills//SKILL.md -~/.config/opencode -~/.config/opencode/AGENTS.md -~/.config/opencode/agents/ -~/.config/opencode/commands/ -~/.config/opencode/modes/ -~/.config/opencode/opencode.json -~/.config/opencode/opencode.jsonc -~/.config/opencode/plugins/ -~/.config/opencode/skills/*/SKILL.md -~/.config/opencode/skills//SKILL.md -~/.config/opencode/themes/*.json -~/.config/opencode/tools/ -~/.config/zed/settings.json -~/.local/share -~/.local/share/opencode/ -~/.local/share/opencode/auth.json -~/.local/share/opencode/log/ -~/.local/share/opencode/mcp-auth.json -~/.local/share/opencode/opencode.jsonc -~/.npmrc -~/.zshrc -~/code/ -~/Library/Application Support -~/projects/* -~/projects/personal/ -${config.github}/blob/dev/packages/sdk/js/src/gen/types.gen.ts -$HOME/intelephense/license.txt -$HOME/projects/* -$XDG_CONFIG_HOME/opencode/themes/*.json -agent/ -agents/ -build/ -commands/ -dist/ -http://:4096 -http://127.0.0.1:8080/callback -http://localhost: -http://localhost:4096 -http://localhost:4096/doc -https://app.example.com -https://AZURE_COGNITIVE_SERVICES_RESOURCE_NAME.cognitiveservices.azure.com/ -https://opencode.ai/zen/v1/chat/completions -https://opencode.ai/zen/v1/messages -https://opencode.ai/zen/v1/models/gemini-3-flash -https://opencode.ai/zen/v1/models/gemini-3-pro -https://opencode.ai/zen/v1/responses -https://RESOURCE_NAME.openai.azure.com/ -laravel/pint -log/ -model: "anthropic/claude-sonnet-4-5" -modes/ -node_modules/ -openai/gpt-4.1 -opencode.ai/config.json -opencode/ -opencode/gpt-5.1-codex -opencode/gpt-5.2-codex -opencode/kimi-k2 -openrouter/google/gemini-2.5-flash -opncd.ai/s/ -packages/*/AGENTS.md -plugins/ -project/ -provider_id/model_id -provider/model -provider/model-id -rm -rf ~/.cache/opencode -skills/ -skills/*/SKILL.md -src/**/*.ts -themes/ -tools/ -``` - -## Keybind strings - -```text -alt+b -Alt+Ctrl+K -alt+d -alt+f -Cmd+Esc -Cmd+Option+K -Cmd+Shift+Esc -Cmd+Shift+G -Cmd+Shift+P -ctrl+a -ctrl+b -ctrl+d -ctrl+e -Ctrl+Esc -ctrl+f -ctrl+g -ctrl+k -Ctrl+Shift+Esc -Ctrl+Shift+P -ctrl+t -ctrl+u -ctrl+w -ctrl+x -DELETE -Shift+Enter -WIN+R -``` - -## Model ID strings referenced - -```text -{env:OPENCODE_MODEL} -anthropic/claude-3-5-sonnet-20241022 -anthropic/claude-haiku-4-20250514 -anthropic/claude-haiku-4-5 -anthropic/claude-sonnet-4-20250514 -anthropic/claude-sonnet-4-5 -gitlab/duo-chat-haiku-4-5 -lmstudio/google/gemma-3n-e4b -openai/gpt-4.1 -openai/gpt-5 -opencode/gpt-5.1-codex -opencode/gpt-5.2-codex -opencode/kimi-k2 -openrouter/google/gemini-2.5-flash -``` diff --git a/.opencode/command/translate.md b/.opencode/command/translate.md new file mode 100644 index 000000000..ed185b1e2 --- /dev/null +++ b/.opencode/command/translate.md @@ -0,0 +1,14 @@ +--- +description: translate English to other languages +model: opencode/claude-opus-4-7 +--- + +run git diff and translate changed english doc and UI copy files to other international languages. Translate all languages in parallel to save time. + +Requirements: + +- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure). +- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks. +- Also preserve every term listed in the Do-Not-Translate glossary below. +- Also apply locale-specific guidance from `.opencode/glossary/.md` when available (for example, `zh-cn.md`). +- Do not modify fenced code blocks. diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index 9f7ed302e..a2e2aacfe 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -144,7 +144,6 @@ https://opencode.ai/zen/v1/models | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -204,15 +203,23 @@ https://opencode.ai/zen/v1/models ### النماذج المهملة -| النموذج | تاريخ الإيقاف | -| ---------------- | ------------- | -| MiniMax M2.1 | 15 مارس 2026 | -| GLM 4.7 | 15 مارس 2026 | -| GLM 4.6 | 15 مارس 2026 | -| Gemini 3 Pro | 9 مارس 2026 | -| Kimi K2 Thinking | 6 مارس 2026 | -| Kimi K2 | 6 مارس 2026 | -| Qwen3 Coder 480B | 6 فبراير 2026 | +| النموذج | تاريخ الإيقاف | +| ------------------ | -------------- | +| GPT 5.2 Codex | 23 يوليو 2026 | +| GPT 5.1 Codex | 23 يوليو 2026 | +| GPT 5.1 Codex Max | 23 يوليو 2026 | +| GPT 5.1 Codex Mini | 23 يوليو 2026 | +| GPT 5 Codex | 23 يوليو 2026 | +| Claude Sonnet 4 | 15 يونيو 2026 | +| GLM 5 | 14 مايو 2026 | +| MiniMax M2.1 | 15 مارس 2026 | +| GLM 4.7 | 15 مارس 2026 | +| GLM 4.6 | 15 مارس 2026 | +| Gemini 3 Pro | 9 مارس 2026 | +| Kimi K2 Thinking | 6 مارس 2026 | +| Kimi K2 | 6 مارس 2026 | +| Claude Haiku 3.5 | 16 فبراير 2026 | +| Qwen3 Coder 480B | 6 فبراير 2026 | --- diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index d10e4f87f..89527763c 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -151,7 +151,6 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -215,15 +214,23 @@ vam ipak može naplatiti više od $20 ako vam stanje padne ispod $5. ### Zastarjeli modeli -| Model | Datum zastarijevanja | -| ---------------- | -------------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Model | Datum zastarijevanja | +| ------------------ | -------------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 44df1602a..009ad4202 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -151,7 +151,6 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -214,15 +213,23 @@ at opkræve dig mere end $20, hvis din saldo kommer under $5. ### Udfasede modeller -| Model | Udfasningsdato | -| ---------------- | -------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Model | Udfasningsdato | +| ------------------ | -------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index 61a03e7c2..11550f61c 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -140,7 +140,6 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -200,15 +199,23 @@ Angenommen, du setzt ein monatliches Nutzungslimit von $20, dann wird Zen in ein ### Veraltete Modelle -| Model | Deprecation date | -| ---------------- | ---------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Model | Deprecation date | +| ------------------ | ---------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index f0df78118..f1a08c7ba 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -151,7 +151,6 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -214,15 +213,23 @@ cobrándote más de $20 si tu saldo baja de $5. ### Modelos obsoletos -| Modelo | Fecha de retirada | -| ---------------- | ----------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Modelo | Fecha de retirada | +| ------------------ | ----------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index 09fa4699a..7710da225 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -140,7 +140,6 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -200,15 +199,23 @@ Par exemple, si vous définissez une limite d'utilisation mensuelle à $20, Zen ### Modèles obsolètes -| Modèle | Date de dépréciation | -| ---------------- | -------------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Modèle | Date de dépréciation | +| ------------------ | -------------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 36c4fd3e8..a3b872553 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -151,7 +151,6 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -214,15 +213,23 @@ per addebitarti più di $20 se il tuo saldo scende sotto $5. ### Modelli deprecati -| Modello | Data di deprecazione | -| ---------------- | -------------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Modello | Data di deprecazione | +| ------------------ | -------------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index 504f1fd78..8fcdc6d46 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -140,7 +140,6 @@ https://opencode.ai/zen/v1/models | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -200,15 +199,23 @@ https://opencode.ai/zen/v1/models ### 非推奨モデル -| Model | Deprecation date | -| ---------------- | ---------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Model | Deprecation date | +| ------------------ | ---------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 222f494bc..eb99c29fe 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -140,7 +140,6 @@ https://opencode.ai/zen/v1/models | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -200,15 +199,23 @@ https://opencode.ai/zen/v1/models ### 지원 중단 모델 -| 모델 | 지원 중단 날짜 | -| ---------------- | -------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| 모델 | 지원 중단 날짜 | +| ------------------ | -------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index c86c282e7..8ab1762e1 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -151,7 +151,6 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -214,15 +213,23 @@ med å belaste deg mer enn $20 hvis saldoen din går under $5. ### Utfasede modeller -| Modell | Utfasingsdato | -| ---------------- | -------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Modell | Utfasingsdato | +| ------------------ | -------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 078888965..52906036c 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -151,7 +151,6 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -213,17 +212,25 @@ ostatecznie obciążyć Cię kwotą wyższą niż $20, jeśli Twoje saldo spadni --- -### Deprecated models +### Wycofane modele -| Model | Deprecation date | -| ---------------- | ---------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Model | Data wycofania | +| ------------------ | -------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index a72a9a90b..b35cfdbde 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -140,7 +140,6 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -200,15 +199,23 @@ Por exemplo, digamos que você defina um limite mensal de uso de $20; o Zen não ### Modelos obsoletos -| Modelo | Data de descontinuação | -| ---------------- | ---------------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Modelo | Data de descontinuação | +| ------------------ | ---------------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 84fedeb10..919026447 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -151,7 +151,6 @@ https://opencode.ai/zen/v1/models | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -214,15 +213,23 @@ https://opencode.ai/zen/v1/models ### Устаревшие модели -| Модель | Дата устаревания | -| ---------------- | ---------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Модель | Дата устаревания | +| ------------------ | ---------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index efb896601..b3914b73c 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -142,7 +142,6 @@ https://opencode.ai/zen/v1/models | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -200,17 +199,25 @@ https://opencode.ai/zen/v1/models --- -### Deprecated models +### โมเดลที่ถูกยกเลิก -| Model | Deprecation date | -| ---------------- | ---------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Model | Deprecation date | +| ------------------ | ---------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index c505662cb..3e53ba40d 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -140,7 +140,6 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -200,15 +199,23 @@ Ayrıca tüm çalışma alanı için ve ekibinizdeki her üye için aylık kulla ### Kullanımdan kaldırılan modeller -| Model | Kullanımdan kaldırılma tarihi | -| ---------------- | ----------------------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Model | Kullanımdan kaldırılma tarihi | +| ------------------ | ----------------------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 6ebf78336..58baceb25 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -151,7 +151,6 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -214,15 +213,23 @@ charging you more than $20 if your balance goes below $5. ### Deprecated models -| Model | Deprecation date | -| ---------------- | ---------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| Model | Deprecation date | +| ------------------ | ---------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index 9248ff174..03124a34f 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -140,7 +140,6 @@ https://opencode.ai/zen/v1/models | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -200,15 +199,23 @@ https://opencode.ai/zen/v1/models ### 已弃用模型 -| 模型 | 弃用日期 | -| ---------------- | -------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| 模型 | 弃用日期 | +| ------------------ | -------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 3be6a3da0..ebd48dea8 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -145,7 +145,6 @@ https://opencode.ai/zen/v1/models | Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 | | 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 | | Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - | | Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - | | Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - | @@ -207,15 +206,23 @@ https://opencode.ai/zen/v1/models ### 已棄用模型 -| 模型 | 棄用日期 | -| ---------------- | -------------- | -| MiniMax M2.1 | March 15, 2026 | -| GLM 4.7 | March 15, 2026 | -| GLM 4.6 | March 15, 2026 | -| Gemini 3 Pro | March 9, 2026 | -| Kimi K2 Thinking | March 6, 2026 | -| Kimi K2 | March 6, 2026 | -| Qwen3 Coder 480B | Feb 6, 2026 | +| 模型 | 棄用日期 | +| ------------------ | -------------- | +| GPT 5.2 Codex | July 23, 2026 | +| GPT 5.1 Codex | July 23, 2026 | +| GPT 5.1 Codex Max | July 23, 2026 | +| GPT 5.1 Codex Mini | July 23, 2026 | +| GPT 5 Codex | July 23, 2026 | +| Claude Sonnet 4 | June 15, 2026 | +| GLM 5 | May 14, 2026 | +| MiniMax M2.1 | March 15, 2026 | +| GLM 4.7 | March 15, 2026 | +| GLM 4.6 | March 15, 2026 | +| Gemini 3 Pro | March 9, 2026 | +| Kimi K2 Thinking | March 6, 2026 | +| Kimi K2 | March 6, 2026 | +| Claude Haiku 3.5 | Feb 16, 2026 | +| Qwen3 Coder 480B | Feb 6, 2026 | --- From 5186c6964b3e7a9a7af31b4e4f0bc29e2fb92d7b Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 26 Apr 2026 13:11:09 -0400 Subject: [PATCH 219/426] sync --- packages/web/src/content/docs/ar/go.mdx | 2 +- packages/web/src/content/docs/bs/go.mdx | 2 +- packages/web/src/content/docs/da/go.mdx | 2 +- packages/web/src/content/docs/de/go.mdx | 2 +- packages/web/src/content/docs/es/go.mdx | 2 +- packages/web/src/content/docs/fr/go.mdx | 2 +- packages/web/src/content/docs/go.mdx | 32 +++++++++++----------- packages/web/src/content/docs/it/go.mdx | 2 +- packages/web/src/content/docs/ja/go.mdx | 2 +- packages/web/src/content/docs/ko/go.mdx | 2 +- packages/web/src/content/docs/nb/go.mdx | 2 +- packages/web/src/content/docs/pl/go.mdx | 2 +- packages/web/src/content/docs/pt-br/go.mdx | 2 +- packages/web/src/content/docs/ru/go.mdx | 2 +- packages/web/src/content/docs/th/go.mdx | 2 +- packages/web/src/content/docs/tr/go.mdx | 2 +- packages/web/src/content/docs/zh-cn/go.mdx | 2 +- packages/web/src/content/docs/zh-tw/go.mdx | 2 +- 18 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 246e86edd..4002e350d 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -93,7 +93,7 @@ OpenCode Go حاليًا في المرحلة التجريبية. | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index c54350696..4722553eb 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -103,7 +103,7 @@ Tabela ispod pruža procijenjeni broj zahtjeva na osnovu tipičnih obrazaca kori | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 20b3ad207..94e267efd 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -103,7 +103,7 @@ Tabellen nedenfor giver et estimeret antal anmodninger baseret på typiske Go-fo | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index b79aa96ab..5b6a792d5 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -95,7 +95,7 @@ Die folgende Tabelle zeigt eine geschätzte Anzahl von Anfragen basierend auf ty | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index 31ba3f45f..87113da9a 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -103,7 +103,7 @@ La siguiente tabla proporciona una cantidad estimada de peticiones basada en los | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 51897e8a4..5754173ea 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -93,7 +93,7 @@ Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur d | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index 8e85fb03c..fca9337f4 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -94,22 +94,22 @@ Limits are defined in dollar value. This means your actual request count depends The table below provides an estimated request count based on typical Go usage patterns: -| Model | requests per 5 hour | requests per week | requests per month | -| ----------------- | ------------------- | ----------------- | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Model | requests per 5 hour | requests per week | requests per month | +| ------------------ | ------------------- | ----------------- | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Estimates are based on observed average request patterns: diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index 03c1c10ca..0bb9a8022 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -101,7 +101,7 @@ La tabella seguente fornisce una stima del conteggio delle richieste in base a p | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index 1a110a700..11fad87f4 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -93,7 +93,7 @@ OpenCode Goには以下の制限が含まれています: | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index 445cd8657..61c546682 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -93,7 +93,7 @@ OpenCode Go에는 다음과 같은 한도가 포함됩니다. | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index ab3676d6d..3d676c0d6 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -103,7 +103,7 @@ Tabellen nedenfor gir et estimert antall forespørsler basert på typiske bruksm | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index b984f0859..cec74defc 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -97,7 +97,7 @@ Poniższa tabela przedstawia szacunkową liczbę żądań na podstawie typowych | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index 2094b5a57..518936bf7 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -103,7 +103,7 @@ A tabela abaixo fornece uma contagem estimada de requisições com base nos padr | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index 0c257153b..e6e141805 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -103,7 +103,7 @@ OpenCode Go включает следующие лимиты: | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index 153f61368..d434c371d 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -93,7 +93,7 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index 77b0eb8c7..2e62d5ba6 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -93,7 +93,7 @@ Aşağıdaki tablo, tipik Go kullanım modellerine dayalı tahmini bir istek say | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index 04156059a..e289cf523 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -93,7 +93,7 @@ OpenCode Go 包含以下限制: | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index a466ccba3..f2f8cd497 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -93,7 +93,7 @@ OpenCode Go 包含以下限制: | MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | | MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2.5 | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | | Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | | MiniMax M2.7 | 3,400 | 8,500 | 17,000 | | MiniMax M2.5 | 6,300 | 15,900 | 31,800 | From bbb56c2a88970ed37817b297376679087dea78ea Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 17:14:24 +0000 Subject: [PATCH 220/426] chore: generate --- packages/web/src/content/docs/ar/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/bs/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/da/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/de/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/es/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/fr/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/it/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/ja/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/ko/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/nb/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/pl/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/pt-br/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/ru/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/th/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/tr/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/zh-cn/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/zh-tw/go.mdx | 30 +++++++++++----------- 17 files changed, 255 insertions(+), 255 deletions(-) diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 4002e350d..9e5d52c60 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -84,22 +84,22 @@ OpenCode Go حاليًا في المرحلة التجريبية. يوضح الجدول أدناه عددًا تقديريًا للطلبات بناءً على أنماط استخدام Go المعتادة: -| Model | الطلبات لكل 5 ساعات | الطلبات في الأسبوع | الطلبات في الشهر | -| ----------------- | ------------------- | ------------------ | ---------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | الطلبات لكل 5 ساعات | الطلبات في الأسبوع | الطلبات في الشهر | +| ------------------ | ------------------- | ------------------ | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | تستند التقديرات إلى متوسطات أنماط الطلبات المرصودة: diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index 4722553eb..86418c335 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -94,22 +94,22 @@ Ograničenja su definisana u dolarskoj vrijednosti. To znači da vaš stvarni br Tabela ispod pruža procijenjeni broj zahtjeva na osnovu tipičnih obrazaca korištenja Go pretplate: -| Model | zahtjeva na 5 sati | zahtjeva sedmično | zahtjeva mjesečno | -| ----------------- | ------------------ | ----------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | zahtjeva na 5 sati | zahtjeva sedmično | zahtjeva mjesečno | +| ------------------ | ------------------ | ----------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Procjene se zasnivaju na zapaženim prosječnim obrascima zahtjeva: diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 94e267efd..70d9e9c8f 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -94,22 +94,22 @@ Grænserne er defineret i dollarværdi. Det betyder, at dit faktiske antal anmod Tabellen nedenfor giver et estimeret antal anmodninger baseret på typiske Go-forbrugsmønstre: -| Model | anmodninger pr. 5 timer | anmodninger pr. uge | anmodninger pr. måned | -| ----------------- | ----------------------- | ------------------- | --------------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | anmodninger pr. 5 timer | anmodninger pr. uge | anmodninger pr. måned | +| ------------------ | ----------------------- | ------------------- | --------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Estimaterne er baseret på observerede gennemsnitlige anmodningsmønstre: diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index 5b6a792d5..7bc9f3e27 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -86,22 +86,22 @@ Limits sind in Dollarwerten definiert. Das bedeutet, dass die tatsächliche Anza Die folgende Tabelle zeigt eine geschätzte Anzahl von Anfragen basierend auf typischen Go-Nutzungsmustern: -| Model | Anfragen pro 5 Stunden | Anfragen pro Woche | Anfragen pro Monat | -| ----------------- | ---------------------- | ------------------ | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | Anfragen pro 5 Stunden | Anfragen pro Woche | Anfragen pro Monat | +| ------------------ | ---------------------- | ------------------ | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Die Schätzungen basieren auf beobachteten durchschnittlichen Anfragemustern: diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index 87113da9a..e346ba2e8 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -94,22 +94,22 @@ Los límites se definen en valor en dólares. Esto significa que tu cantidad rea La siguiente tabla proporciona una cantidad estimada de peticiones basada en los patrones típicos de uso de Go: -| Model | peticiones por 5 horas | peticiones por semana | peticiones por mes | -| ----------------- | ---------------------- | --------------------- | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | peticiones por 5 horas | peticiones por semana | peticiones por mes | +| ------------------ | ---------------------- | --------------------- | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Las estimaciones se basan en los patrones de peticiones promedio observados: diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 5754173ea..219a17ebf 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -84,22 +84,22 @@ Les limites sont définies en valeur monétaire (dollars). Cela signifie que vot Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur des modèles d'utilisation typiques de Go : -| Model | requêtes par 5 heures | requêtes par semaine | requêtes par mois | -| ----------------- | --------------------- | -------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | requêtes par 5 heures | requêtes par semaine | requêtes par mois | +| ------------------ | --------------------- | -------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Les estimations sont basées sur les modèles de requêtes moyens observés : diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index 0bb9a8022..023ee053a 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -92,22 +92,22 @@ I limiti sono definiti in valore in dollari. Questo significa che il conteggio e La tabella seguente fornisce una stima del conteggio delle richieste in base a pattern di utilizzo tipici di Go: -| Model | richieste ogni 5 ore | richieste a settimana | richieste al mese | -| ----------------- | -------------------- | --------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | richieste ogni 5 ore | richieste a settimana | richieste al mese | +| ------------------ | -------------------- | --------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Le stime si basano sui pattern medi di richieste osservati: diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index 11fad87f4..207a47dd9 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -84,22 +84,22 @@ OpenCode Goには以下の制限が含まれています: 以下の表は、一般的なGoの利用パターンに基づいた推定リクエスト数を示しています: -| Model | 5時間あたりのリクエスト数 | 週間リクエスト数 | 月間リクエスト数 | -| ----------------- | ------------------------- | ---------------- | ---------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | 5時間あたりのリクエスト数 | 週間リクエスト数 | 月間リクエスト数 | +| ------------------ | ------------------------- | ---------------- | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | 推定値は、観測された平均的なリクエストパターンに基づいています: diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index 61c546682..e9d117232 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -84,22 +84,22 @@ OpenCode Go에는 다음과 같은 한도가 포함됩니다. 아래 표는 일반적인 Go 사용 패턴을 기준으로 한 예상 요청 횟수를 보여줍니다. -| Model | 5시간당 요청 횟수 | 주간 요청 횟수 | 월간 요청 횟수 | -| ----------------- | ----------------- | -------------- | -------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | 5시간당 요청 횟수 | 주간 요청 횟수 | 월간 요청 횟수 | +| ------------------ | ----------------- | -------------- | -------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | 예상치는 관찰된 평균 요청 패턴을 기준으로 합니다. diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index 3d676c0d6..9191b94a3 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -94,22 +94,22 @@ Grensene er definert i dollarverdi. Dette betyr at ditt faktiske antall forespø Tabellen nedenfor gir et estimert antall forespørsler basert på typiske bruksmønstre for Go: -| Model | forespørsler per 5 timer | forespørsler per uke | forespørsler per måned | -| ----------------- | ------------------------ | -------------------- | ---------------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | forespørsler per 5 timer | forespørsler per uke | forespørsler per måned | +| ------------------ | ------------------------ | -------------------- | ---------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Estimatene er basert på observerte gjennomsnittlige forespørselsmønstre: diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index cec74defc..00771c024 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -88,22 +88,22 @@ Limity są zdefiniowane w wartości w dolarach. Oznacza to, że rzeczywista licz Poniższa tabela przedstawia szacunkową liczbę żądań na podstawie typowych wzorców korzystania z Go: -| Model | żądania na 5 godzin | żądania na tydzień | żądania na miesiąc | -| ----------------- | ------------------- | ------------------ | ------------------ | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | żądania na 5 godzin | żądania na tydzień | żądania na miesiąc | +| ------------------ | ------------------- | ------------------ | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Szacunki opierają się na zaobserwowanych średnich wzorcach żądań: diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index 518936bf7..9fbdd69f3 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -94,22 +94,22 @@ Os limites são definidos em valor em dólares. Isso significa que a sua contage A tabela abaixo fornece uma contagem estimada de requisições com base nos padrões típicos de uso do Go: -| Model | requisições por 5 horas | requisições por semana | requisições por mês | -| ----------------- | ----------------------- | ---------------------- | ------------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | requisições por 5 horas | requisições por semana | requisições por mês | +| ------------------ | ----------------------- | ---------------------- | ------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | As estimativas baseiam-se nos padrões médios de requisições observados: diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index e6e141805..e91bc331d 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -94,22 +94,22 @@ OpenCode Go включает следующие лимиты: В таблице ниже приведено примерное количество запросов на основе типичных сценариев использования Go: -| Model | запросов за 5 часов | запросов в неделю | запросов в месяц | -| ----------------- | ------------------- | ----------------- | ---------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | запросов за 5 часов | запросов в неделю | запросов в месяц | +| ------------------ | ------------------- | ----------------- | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Оценки основаны на наблюдаемых средних показателях запросов: diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index d434c371d..0fb7020f3 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -84,22 +84,22 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: ตารางด้านล่างแสดงจำนวน request โดยประมาณตามรูปแบบการใช้งานปกติของ Go: -| Model | requests ต่อ 5 ชั่วโมง | requests ต่อสัปดาห์ | requests ต่อเดือน | -| ----------------- | ---------------------- | ------------------- | ----------------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | requests ต่อ 5 ชั่วโมง | requests ต่อสัปดาห์ | requests ต่อเดือน | +| ------------------ | ---------------------- | ------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | การประมาณการอ้างอิงจากรูปแบบการใช้งาน request โดยเฉลี่ยที่สังเกตพบ: diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index 2e62d5ba6..37c5268b4 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -84,22 +84,22 @@ Limitler dolar değeri üzerinden belirlenmiştir. Bu, gerçek istek sayınızı Aşağıdaki tablo, tipik Go kullanım modellerine dayalı tahmini bir istek sayısı sunmaktadır: -| Model | 5 saatte bir istek | haftalık istek | aylık istek | -| ----------------- | ------------------ | -------------- | ----------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | 5 saatte bir istek | haftalık istek | aylık istek | +| ------------------ | ------------------ | -------------- | ----------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | Tahminler, gözlemlenen ortalama istek modellerine dayanmaktadır: diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index e289cf523..c5d4a381a 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -84,22 +84,22 @@ OpenCode Go 包含以下限制: 下表提供了基于典型 Go 使用模式的预估请求数: -| Model | 每 5 小时请求数 | 每周请求数 | 每月请求数 | -| ----------------- | --------------- | ---------- | ---------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | 每 5 小时请求数 | 每周请求数 | 每月请求数 | +| ------------------ | --------------- | ---------- | ---------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | 预估值基于观察到的平均请求模式: diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index f2f8cd497..290bdac36 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -84,22 +84,22 @@ OpenCode Go 包含以下限制: 下表提供了基於典型 Go 使用模式的預估請求次數: -| Model | 每 5 小時請求數 | 每週請求數 | 每月請求數 | -| ----------------- | --------------- | ---------- | ---------- | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| GLM-5 | 1,150 | 2,880 | 5,750 | -| Kimi K2.5 | 1,850 | 4,630 | 9,250 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | -| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | -| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | +| Model | 每 5 小時請求數 | 每週請求數 | 每月請求數 | +| ------------------ | --------------- | ---------- | ---------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| MiMo-V2.5-Pro | 1,290 | 3,225 | 6,450 | | MiMo-V2.5 (≤ 256K) | 2,150 | 5,450 | 10,900 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | -| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | -| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | -| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | +| DeepSeek V4 Pro | 1,300 | 3,250 | 6,500 | +| DeepSeek V4 Flash | 7,450 | 18,600 | 37,300 | 預估值是基於觀察到的平均請求模式: From 00d1a7e090e3b7cc7d3a39e874a02865439d8472 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Sun, 26 Apr 2026 12:32:41 -0500 Subject: [PATCH 221/426] chore: rm empty file --- session.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 session.json diff --git a/session.json b/session.json deleted file mode 100644 index e69de29bb..000000000 From dcee1c36426495716b2bacc7f31ccf54d29a3ac0 Mon Sep 17 00:00:00 2001 From: Jermiah Joseph <44614774+jjjermiah@users.noreply.github.com> Date: Sun, 26 Apr 2026 14:21:29 -0400 Subject: [PATCH 222/426] fix(editor): reject lock files with no workspace match for cwd (#24323) --- .../src/cli/cmd/tui/context/editor.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/editor.ts b/packages/opencode/src/cli/cmd/tui/context/editor.ts index 75c5440f5..72b0785d6 100644 --- a/packages/opencode/src/cli/cmd/tui/context/editor.ts +++ b/packages/opencode/src/cli/cmd/tui/context/editor.ts @@ -278,12 +278,16 @@ function resolveEditorLockFile() { } const cwd = process.cwd() + // longest workspace folder that contains cwd; 0 if none match + const bestMatchLength = (lock: EditorLockFile) => + Math.max(0, ...lock.workspaceFolders.map((folder) => pathContainsLength(folder, cwd))) const locks = entries .filter((entry) => entry.endsWith(".lock")) .map((entry) => readEditorLockFile(path.join(directory, entry))) .filter((entry): entry is EditorLockFile => Boolean(entry)) - .sort((left, right) => scoreEditorLock(right, cwd) - scoreEditorLock(left, cwd)) - + .filter((entry) => bestMatchLength(entry) > 0) + // prefer locks with longer matching workspace folders, then more recent ones + .sort((left, right) => bestMatchLength(right) - bestMatchLength(left) || right.mtimeMs - left.mtimeMs) return locks[0] } @@ -310,11 +314,6 @@ function readEditorLockFile(filePath: string): EditorLockFile | undefined { } } -function scoreEditorLock(lock: EditorLockFile, cwd: string) { - const workspaceMatch = lock.workspaceFolders.some((folder) => pathContains(folder, cwd)) ? 1 : 0 - return workspaceMatch * 1_000_000_000_000 + lock.mtimeMs -} - function editorSelectionKey(selection: EditorSelection | undefined) { if (!selection) return "" return [ @@ -327,9 +326,10 @@ function editorSelectionKey(selection: EditorSelection | undefined) { ].join("\0") } -function pathContains(parent: string, child: string) { - const relative = path.relative(path.resolve(parent), path.resolve(child)) - return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)) +function pathContainsLength(parent: string, child: string) { + const resolved = path.resolve(parent) + const relative = path.relative(resolved, path.resolve(child)) + return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)) ? resolved.length : 0 } function openEditorSocket(connection: EditorConnection) { From 3beadeebff13fefb6f771ce22c3ef7b2736e8245 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 27 Apr 2026 02:46:49 +0800 Subject: [PATCH 223/426] feat(go): add Go model listing endpoint (#24304) Co-authored-by: Frank --- .../app/src/routes/zen/go/v1/models.ts | 10 +++++ .../app/src/routes/zen/util/modelsHandler.ts | 36 +++++++++++++++ .../console/app/src/routes/zen/v1/models.ts | 44 +++---------------- packages/web/src/content/docs/ar/go.mdx | 10 +++++ packages/web/src/content/docs/bs/go.mdx | 10 +++++ packages/web/src/content/docs/da/go.mdx | 10 +++++ packages/web/src/content/docs/de/go.mdx | 10 +++++ packages/web/src/content/docs/es/go.mdx | 10 +++++ packages/web/src/content/docs/fr/go.mdx | 10 +++++ packages/web/src/content/docs/go.mdx | 10 +++++ packages/web/src/content/docs/it/go.mdx | 10 +++++ packages/web/src/content/docs/ja/go.mdx | 10 +++++ packages/web/src/content/docs/ko/go.mdx | 10 +++++ packages/web/src/content/docs/nb/go.mdx | 10 +++++ packages/web/src/content/docs/pl/go.mdx | 10 +++++ packages/web/src/content/docs/pt-br/go.mdx | 10 +++++ packages/web/src/content/docs/ru/go.mdx | 10 +++++ packages/web/src/content/docs/th/go.mdx | 10 +++++ packages/web/src/content/docs/tr/go.mdx | 10 +++++ packages/web/src/content/docs/zh-cn/go.mdx | 10 +++++ packages/web/src/content/docs/zh-tw/go.mdx | 10 +++++ 21 files changed, 233 insertions(+), 37 deletions(-) create mode 100644 packages/console/app/src/routes/zen/go/v1/models.ts create mode 100644 packages/console/app/src/routes/zen/util/modelsHandler.ts diff --git a/packages/console/app/src/routes/zen/go/v1/models.ts b/packages/console/app/src/routes/zen/go/v1/models.ts new file mode 100644 index 000000000..85db30688 --- /dev/null +++ b/packages/console/app/src/routes/zen/go/v1/models.ts @@ -0,0 +1,10 @@ +import type { APIEvent } from "@solidjs/start/server" +import { getHandler, optionsHandler } from "../../util/modelsHandler" + +export async function OPTIONS(_input: APIEvent) { + return optionsHandler() +} + +export async function GET(input: APIEvent) { + return getHandler({ modelList: "lite" }) +} diff --git a/packages/console/app/src/routes/zen/util/modelsHandler.ts b/packages/console/app/src/routes/zen/util/modelsHandler.ts new file mode 100644 index 000000000..d924e25c8 --- /dev/null +++ b/packages/console/app/src/routes/zen/util/modelsHandler.ts @@ -0,0 +1,36 @@ +import { ZenData } from "@opencode-ai/console-core/model.js" + +export async function optionsHandler() { + return new Response(null, { + status: 200, + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization", + }, + }) +} + +export async function getHandler(opts: { modelList: "lite" | "full"; disabledModels?: string[] }) { + const zenData = ZenData.list(opts.modelList) + + return new Response( + JSON.stringify({ + object: "list", + data: Object.entries(zenData.models) + .filter(([id]) => !opts.disabledModels?.includes(id)) + .filter(([id]) => !id.startsWith("alpha-")) + .map(([id, _model]) => ({ + id, + object: "model", + created: Math.floor(Date.now() / 1000), + owned_by: "opencode", + })), + }), + { + headers: { + "Content-Type": "application/json", + }, + }, + ) +} diff --git a/packages/console/app/src/routes/zen/v1/models.ts b/packages/console/app/src/routes/zen/v1/models.ts index 6b4a878fc..d2fc36e45 100644 --- a/packages/console/app/src/routes/zen/v1/models.ts +++ b/packages/console/app/src/routes/zen/v1/models.ts @@ -3,59 +3,29 @@ import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/ind import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js" import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js" import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js" -import { ZenData } from "@opencode-ai/console-core/model.js" +import { optionsHandler, getHandler } from "~/routes/zen/util/modelsHandler" export async function OPTIONS(_input: APIEvent) { - return new Response(null, { - status: 200, - headers: { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, POST, OPTIONS", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - }, - }) + return optionsHandler() } export async function GET(input: APIEvent) { - const zenData = ZenData.list("full") - const disabledModels = await authenticate() - - return new Response( - JSON.stringify({ - object: "list", - data: Object.entries(zenData.models) - .filter(([id]) => !disabledModels.includes(id)) - .filter(([id]) => !id.startsWith("alpha-")) - .map(([id, _model]) => ({ - id, - object: "model", - created: Math.floor(Date.now() / 1000), - owned_by: "opencode", - })), - }), - { - headers: { - "Content-Type": "application/json", - }, - }, - ) - - async function authenticate() { + const disabledModels = await (() => { const apiKey = input.request.headers.get("authorization")?.split(" ")[1] if (!apiKey) return [] - const disabledModels = await Database.use((tx) => + return Database.use((tx) => tx .select({ model: ModelTable.model, }) .from(KeyTable) .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID)) - .leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), isNull(ModelTable.timeDeleted))) + .innerJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), isNull(ModelTable.timeDeleted))) .where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted))) .then((rows) => rows.map((row) => row.model)), ) + })() - return disabledModels - } + return getHandler({ modelList: "full", disabledModels }) } diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 9e5d52c60..f760be36b 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -155,6 +155,16 @@ OpenCode Go حاليًا في المرحلة التجريبية. --- +### النماذج + +يمكنك جلب القائمة الكاملة بالنماذج المتاحة وبياناتها الوصفية من: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## الخصوصية صُمّمت هذه الخطة أساسًا للمستخدمين الدوليين، مع استضافة النماذج في الولايات المتحدة والاتحاد الأوروبي وسنغافورة لضمان وصول عالمي مستقر. ويتّبع مزودونا سياسة عدم الاحتفاظ بالبيانات، ولا يستخدمون بياناتك لتدريب النماذج. diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index 86418c335..7977725ac 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -169,6 +169,16 @@ koristi format `opencode-go/`. Na primjer, za Kimi K2.6, koristili bis --- +### Modeli + +Pun spisak dostupnih modela i njihovih metapodataka možete preuzeti na: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Privatnost Plan je prvenstveno namijenjen međunarodnim korisnicima, a modeli su smješteni u US, EU i Singaporeu radi stabilnog globalnog pristupa. Naši pružaoci usluga primjenjuju politiku nultog zadržavanja podataka i ne koriste vaše podatke za treniranje modela. diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 70d9e9c8f..123e23c41 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -169,6 +169,16 @@ bruge `opencode-go/kimi-k2.6` i din config. --- +### Modeller + +Du kan hente den fulde liste over tilgængelige modeller og deres metadata fra: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Privatliv Planen er primært designet til internationale brugere med modeller hostet i US, EU og Singapore for stabil global adgang. Vores udbydere følger en zero-retention-policy og bruger ikke dine data til modeltræning. diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index 7bc9f3e27..fc49b9a71 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -157,6 +157,16 @@ Die [Modell-ID](/docs/config/#models) in deiner OpenCode Config verwendet das Fo --- +### Models + +Du kannst die vollständige Liste der verfügbaren Modelle und ihrer Metadaten hier abrufen: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Datenschutz Der Plan ist in erster Linie für internationale Nutzer konzipiert, mit in US, EU und Singapore gehosteten Modellen für einen stabilen weltweiten Zugriff. Unsere Anbieter befolgen eine Zero-Retention-Richtlinie und verwenden Ihre Daten nicht für das Modelltraining. diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index e346ba2e8..55068577b 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -169,6 +169,16 @@ usa el formato `opencode-go/`. Por ejemplo, para Kimi K2.6, usarías --- +### Modelos + +Puedes obtener la lista completa de modelos disponibles y sus metadatos desde: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Privacidad El plan está diseñado principalmente para usuarios internacionales, con modelos alojados en US, EU y Singapore para ofrecer un acceso global estable. Nuestros proveedores siguen una política de retención cero y no utilizan tus datos para el entrenamiento de modelos. diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 219a17ebf..8ab2d04df 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -155,6 +155,16 @@ L'[ID de modèle](/docs/config/#models) dans votre configuration OpenCode utilis --- +### Modèles + +Vous pouvez récupérer la liste complète des modèles disponibles et leurs métadonnées à partir de : + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Confidentialité Cette offre est conçue avant tout pour les utilisateurs internationaux, avec des modèles hébergés aux US, dans l’EU et à Singapore afin d’assurer un accès mondial stable. Nos fournisseurs appliquent une politique de rétention zéro et n’utilisent pas vos données pour l’entraînement des modèles. diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index fca9337f4..b12d29c48 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -169,6 +169,16 @@ use `opencode-go/kimi-k2.6` in your config. --- +### Models + +You can fetch the full list of available models and their metadata from: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Privacy The plan is designed primarily for international users, with models hosted in the US, EU, and Singapore for stable global access. Our providers follow a zero-retention policy and do not use your data for model training. diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index 023ee053a..7c31325b1 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -167,6 +167,16 @@ utilizza il formato `opencode-go/`. Ad esempio, per Kimi K2.6, userest --- +### Modelli + +Puoi recuperare l'elenco completo dei modelli disponibili e i relativi metadati da: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Privacy Il piano è pensato principalmente per gli utenti internazionali, con modelli ospitati negli US, nell’EU e a Singapore per un accesso globale stabile. I nostri provider seguono una politica di zero-retention e non utilizzano i tuoi dati per l’addestramento dei modelli. diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index 207a47dd9..ddfbcd93e 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -155,6 +155,16 @@ OpenCode設定の[model id](/docs/config/#models)は、`opencode-go/` --- +### モデル + +利用可能なモデルとそのメタデータの完全な一覧は、次から取得できます。 + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## プライバシー このプランは主に海外ユーザー向けに設計されており、安定したグローバルアクセスのため、モデルは US、EU、Singapore でホストされています。各プロバイダーはデータを保持しないポリシーに従っており、お客様のデータをモデルのトレーニングに使用することはありません。 diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index e9d117232..23463b61a 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -155,6 +155,16 @@ OpenCode config의 [model id](/docs/config/#models)는 `opencode-go/` --- +### 모델 + +사용 가능한 전체 모델 목록과 메타데이터는 다음에서 가져올 수 있습니다. + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## 개인정보 보호 이 플랜은 안정적인 전 세계 액세스를 위해 모델을 미국, EU, 싱가포르에 호스팅하며, 주로 해외 사용자를 위해 설계되었습니다. 저희 제공자는 zero-retention 정책을 따르며, 고객 데이터를 모델 학습에 사용하지 않습니다. diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index 9191b94a3..9caf39990 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -169,6 +169,16 @@ bruke `opencode-go/kimi-k2.6` i konfigurasjonen din. --- +### Modeller + +Du kan hente hele listen over tilgjengelige modeller og metadataene deres fra: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Personvern Planen er primært utformet for internasjonale brukere, med modeller hostet i US, EU og Singapore for stabil global tilgang. Våre leverandører følger en zero-retention-policy og bruker ikke dataene dine til modelltrening. diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index 00771c024..e9cf4f7a5 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -161,6 +161,16 @@ używa formatu `opencode-go/`. Na przykład dla Kimi K2.6 należy uży --- +### Modele + +Pełną listę dostępnych modeli i ich metadane możesz pobrać z: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Prywatność Plan został zaprojektowany przede wszystkim z myślą o użytkownikach międzynarodowych, a modele są hostowane w US, EU i Singapore, aby zapewnić stabilny dostęp na całym świecie. Nasi dostawcy stosują politykę zerowej retencji i nie wykorzystują Twoich danych do trenowania modeli. diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index 9fbdd69f3..7e3d3f9a2 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -169,6 +169,16 @@ usa o formato `opencode-go/`. Por exemplo, para o Kimi K2.6, você usa --- +### Modelos + +Você pode buscar a lista completa de modelos disponíveis e seus metadados em: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Privacidade O plano foi projetado principalmente para usuários internacionais, com modelos hospedados em US, EU e Singapore para garantir acesso global estável. Nossos provedores seguem uma política de retenção zero e não usam seus dados para treinamento de modelos. diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index e91bc331d..9ff1ae141 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -169,6 +169,16 @@ OpenCode Go включает следующие лимиты: --- +### Модели + +Вы можете получить полный список доступных моделей и их метаданных по адресу: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Конфиденциальность Этот план разработан в первую очередь для международных пользователей: модели размещены в US, EU и Singapore, чтобы обеспечить стабильный доступ по всему миру. Наши провайдеры придерживаются политики zero-retention и не используют ваши данные для обучения моделей. diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index 0fb7020f3..9a5d4c634 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -155,6 +155,16 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: --- +### Models + +คุณสามารถดึงรายการโมเดลทั้งหมดที่พร้อมใช้งานและ metadata ของมันได้จาก: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Privacy แพลนนี้ออกแบบมาสำหรับผู้ใช้ทั่วโลกเป็นหลัก โดยโฮสต์โมเดลไว้ใน US, EU และ Singapore เพื่อให้เข้าถึงได้อย่างเสถียรจากทั่วโลก ผู้ให้บริการของเราปฏิบัติตามนโยบาย zero-retention และไม่นำข้อมูลของคุณไปใช้ในการฝึกโมเดล diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index 37c5268b4..ab488eb25 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -155,6 +155,16 @@ OpenCode yapılandırmanızdaki [model id](/docs/config/#models) formatı `openc --- +### Modeller + +Mevcut modellerin tam listesini ve metadata'larını şuradan alabilirsiniz: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## Gizlilik Plan, öncelikle uluslararası kullanıcılar için tasarlanmıştır; dünya genelinde istikrarlı erişim sağlamak için modeller US, EU ve Singapore'da barındırılır. Sağlayıcılarımız sıfır veri saklama politikası uygular ve verilerinizi model eğitimi için kullanmaz. diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index c5d4a381a..10ec97778 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -155,6 +155,16 @@ OpenCode Go 包含以下限制: --- +### 模型 + +你可以从以下地址获取可用模型及其元数据的完整列表: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## 隐私保护 该方案主要面向国际用户,模型托管在 US、EU 和 Singapore,以提供稳定的全球访问。我们的提供商遵循零保留政策,不会将您的数据用于模型训练。 diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index 290bdac36..9e460b967 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -155,6 +155,16 @@ OpenCode Go 包含以下限制: --- +### 模型 + +你可以從以下位置取得所有可用模型及其中繼資料的完整清單: + +``` +https://opencode.ai/zen/go/v1/models +``` + +--- + ## 隱私權 此方案主要為國際使用者設計,模型部署於 US、EU 與 Singapore,以提供穩定的全球存取體驗。我們的供應商遵循零保留政策,不會將你的資料用於模型訓練。 From 21e01dbe04acc49fb758abb91bf3ec80dd8a90b1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 26 Apr 2026 22:31:33 +0200 Subject: [PATCH 224/426] upgrade opentui to 0.1.104 (#24531) --- bun.lock | 24 ++--- package.json | 4 +- .../opencode/src/cli/cmd/tui/util/index.ts | 1 - .../opencode/src/cli/cmd/tui/util/terminal.ts | 96 ------------------- packages/plugin/package.json | 4 +- 5 files changed, 16 insertions(+), 113 deletions(-) delete mode 100644 packages/opencode/src/cli/cmd/tui/util/terminal.ts diff --git a/bun.lock b/bun.lock index 6a146d9e8..8a3684c0b 100644 --- a/bun.lock +++ b/bun.lock @@ -506,8 +506,8 @@ "typescript": "catalog:", }, "peerDependencies": { - "@opentui/core": ">=0.1.103", - "@opentui/solid": ">=0.1.103", + "@opentui/core": ">=0.1.104", + "@opentui/solid": ">=0.1.104", }, "optionalPeers": [ "@opentui/core", @@ -685,8 +685,8 @@ "@npmcli/arborist": "9.4.0", "@octokit/rest": "22.0.0", "@openauthjs/openauth": "0.0.0-20250322224806", - "@opentui/core": "0.1.103", - "@opentui/solid": "0.1.103", + "@opentui/core": "0.1.104", + "@opentui/solid": "0.1.104", "@pierre/diffs": "1.1.0-beta.18", "@playwright/test": "1.59.1", "@solid-primitives/storage": "4.3.3", @@ -1613,21 +1613,21 @@ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], - "@opentui/core": ["@opentui/core@0.1.103", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.103", "@opentui/core-darwin-x64": "0.1.103", "@opentui/core-linux-arm64": "0.1.103", "@opentui/core-linux-x64": "0.1.103", "@opentui/core-win32-arm64": "0.1.103", "@opentui/core-win32-x64": "0.1.103", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-PWVv/bDmlk1i6X1f0zXs+jSaTrQ/ByX8wFbP2WinOObTGf//UbcRP4dbWxPXvOyka9QlmRBG/7GbloQSIStyVw=="], + "@opentui/core": ["@opentui/core@0.1.104", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.104", "@opentui/core-darwin-x64": "0.1.104", "@opentui/core-linux-arm64": "0.1.104", "@opentui/core-linux-x64": "0.1.104", "@opentui/core-win32-arm64": "0.1.104", "@opentui/core-win32-x64": "0.1.104", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-29RZ+f7sw5B6ebDFRuhrDs2UOV7vR76npr0vVRAJAmIMtdLb4Dse35Y0Hk6WerFKNuX4ajlQK4eO3oAY/29KLQ=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.103", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lxCyedDkcen12IgBtXjkJ7iY66xa7VC4nxRNKCUeLY2ZP9hUE1AsDtbyQzqY+BQadsI/ZME9STzaHDCUFg0TpA=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.104", "", { "os": "darwin", "cpu": "arm64" }, "sha512-83Bf+LOLYCgWccAuPzftchs8LIoTzYU8f8CM7GeUFqH0VLKs2Ey+TtCTsBWpeDklOal0XFQr5begotqc/ldPVg=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.103", "", { "os": "darwin", "cpu": "x64" }, "sha512-QMYD+zUDGQliJ6m5nuNvA72jtluFeyVMoHkuA5m/Xmed/u8eLfahAKmDj3kY66ntUroPHWevcpbpvd7NCFEoFQ=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.104", "", { "os": "darwin", "cpu": "x64" }, "sha512-TAHRtEi7Gz2O3TXolPCiWpXsoapKllGCl74bOuJsyipMwfk3wuUu6SeqBPC/cmVJlp143dC8kcGLwLsd3dxsgA=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.103", "", { "os": "linux", "cpu": "arm64" }, "sha512-GzOvNr9dN6JaQ9qs7m8E75wLAHwT5CyxqkE6rEr1BO23/d2Ix7e3GYw/JRY5VnTge+eXrfDVbqNtPcQamUNiEA=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.104", "", { "os": "linux", "cpu": "arm64" }, "sha512-WJ8ssxcRpoHWgGLDYlVjlYZGkmA2G9melgXTrzk6CbwhW5tVAE6AmW5Kq9DUHclYssCpNWTvZFMDsVsf5kn2Xw=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.103", "", { "os": "linux", "cpu": "x64" }, "sha512-odywllco5zUKNc60uD3JKaCybK64u6BfmpScs4a8Qn89yH/yk23bzWXDRWaGgQdY65L2/VCbcGs1ezA1S/2YTw=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.104", "", { "os": "linux", "cpu": "x64" }, "sha512-I4DurCkJXJxxGwAq1wGZ1vzXh57GxdzljWeCJ5sIPy7coICn+/cWRw9gE9VxiWTa3T85l4YN0UFSQx7IFCEbvg=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.103", "", { "os": "win32", "cpu": "arm64" }, "sha512-tZL5w3Y0JnO7RkIvfuNDAzJn0j6+JIYl6M8DgPM5p8AQt+162S8LmbumzmqQLZl4cEev2eN7/tw72WIk6b+/CQ=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.104", "", { "os": "win32", "cpu": "arm64" }, "sha512-PDwqGHlVUiAiZa8GXiPPJVWIxucQ5+2FLcK4C76VT8HJzUXiUEGJFtuvi9oY7k29p8olcFHW75Gj1WMUrw+JKw=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.103", "", { "os": "win32", "cpu": "x64" }, "sha512-wqnibt/OE5ldSzVPxEbriA0TjI2B11CJl4uJoLxTZ47KDx7tAFIMdhBf9IRAGNCSbcDuZ8ZGEFhV+SLaftkrlw=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.104", "", { "os": "win32", "cpu": "x64" }, "sha512-fQGMxFshk/i7KskqaK5lB8u2K8Lx/LO9+PdfKu/+GBIMKmElpJXQ8Bq2sMf+7COcoXcjAjUJn8cpTyIqbFxAyQ=="], - "@opentui/solid": ["@opentui/solid@0.1.103", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.103", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-L28WFBs17Z5JXkJhPagLy8tUamkttDaaQDdb2KEO01IQ9r81yLBRpYqD/lHp6UoSISXgwQVDQ9yNtxwR1BQZvQ=="], + "@opentui/solid": ["@opentui/solid@0.1.104", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.104", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.10", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.11" } }, "sha512-Mma1OR3s0QZayRGdbdNv1m3H6IExRrF9QRUNWEZzDOqo3oNxTxaY9K7Qk6eGrk0unMjwwE113TSOdnG80RwYnQ=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], diff --git a/package.json b/package.json index b2c8a2d7a..b8f90d975 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "@types/cross-spawn": "6.0.6", "@octokit/rest": "22.0.0", "@hono/zod-validator": "0.4.2", - "@opentui/core": "0.1.103", - "@opentui/solid": "0.1.103", + "@opentui/core": "0.1.104", + "@opentui/solid": "0.1.104", "ulid": "3.0.1", "@kobalte/core": "0.13.11", "@types/luxon": "3.7.1", diff --git a/packages/opencode/src/cli/cmd/tui/util/index.ts b/packages/opencode/src/cli/cmd/tui/util/index.ts index a0bdbc3c2..940f18c23 100644 --- a/packages/opencode/src/cli/cmd/tui/util/index.ts +++ b/packages/opencode/src/cli/cmd/tui/util/index.ts @@ -1,5 +1,4 @@ export * as Editor from "./editor" export * as Selection from "./selection" export * as Sound from "./sound" -export * as Terminal from "./terminal" export * as Clipboard from "./clipboard" diff --git a/packages/opencode/src/cli/cmd/tui/util/terminal.ts b/packages/opencode/src/cli/cmd/tui/util/terminal.ts deleted file mode 100644 index c026b7381..000000000 --- a/packages/opencode/src/cli/cmd/tui/util/terminal.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { RGBA } from "@opentui/core" - -export type Colors = Awaited> - -function parse(color: string): RGBA | null { - if (color.startsWith("rgb:")) { - const parts = color.substring(4).split("/") - return RGBA.fromInts(parseInt(parts[0], 16) >> 8, parseInt(parts[1], 16) >> 8, parseInt(parts[2], 16) >> 8, 255) - } - if (color.startsWith("#")) { - return RGBA.fromHex(color) - } - if (color.startsWith("rgb(")) { - const parts = color.substring(4, color.length - 1).split(",") - return RGBA.fromInts(parseInt(parts[0]), parseInt(parts[1]), parseInt(parts[2]), 255) - } - return null -} - -/** - * Query terminal colors including background, foreground, and palette (0-15). - * Uses OSC escape sequences to retrieve actual terminal color values. - * - * Note: OSC 4 (palette) queries may not work through tmux as responses are filtered. - * OSC 10/11 (foreground/background) typically work in most environments. - * - * Returns an object with background, foreground, and colors array. - * Any query that fails will be null/empty. - */ -export async function colors(): Promise<{ - background: RGBA | null - foreground: RGBA | null - colors: RGBA[] -}> { - if (!process.stdin.isTTY) return { background: null, foreground: null, colors: [] } - - return new Promise((resolve) => { - let background: RGBA | null = null - let foreground: RGBA | null = null - const paletteColors: RGBA[] = [] - let timeout: NodeJS.Timeout - - const cleanup = () => { - process.stdin.setRawMode(false) - process.stdin.removeListener("data", handler) - clearTimeout(timeout) - } - - const handler = (data: Buffer) => { - const str = data.toString() - - // Match OSC 11 (background color) - const bgMatch = str.match(/\x1b]11;([^\x07\x1b]+)/) - if (bgMatch) { - background = parse(bgMatch[1]) - } - - // Match OSC 10 (foreground color) - const fgMatch = str.match(/\x1b]10;([^\x07\x1b]+)/) - if (fgMatch) { - foreground = parse(fgMatch[1]) - } - - // Match OSC 4 (palette colors) - const paletteMatches = str.matchAll(/\x1b]4;(\d+);([^\x07\x1b]+)/g) - for (const match of paletteMatches) { - const index = parseInt(match[1]) - const color = parse(match[2]) - if (color) paletteColors[index] = color - } - - // Return immediately if we have all 16 palette colors - if (paletteColors.filter((c) => c !== undefined).length === 16) { - cleanup() - resolve({ background, foreground, colors: paletteColors }) - } - } - - process.stdin.setRawMode(true) - process.stdin.on("data", handler) - - // Query background (OSC 11) - process.stdout.write("\x1b]11;?\x07") - // Query foreground (OSC 10) - process.stdout.write("\x1b]10;?\x07") - // Query palette colors 0-15 (OSC 4) - for (let i = 0; i < 16; i++) { - process.stdout.write(`\x1b]4;${i};?\x07`) - } - - timeout = setTimeout(() => { - cleanup() - resolve({ background, foreground, colors: paletteColors }) - }, 1000) - }) -} diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 88f7a65f6..af13724cd 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -22,8 +22,8 @@ "zod": "catalog:" }, "peerDependencies": { - "@opentui/core": ">=0.1.103", - "@opentui/solid": ">=0.1.103" + "@opentui/core": ">=0.1.104", + "@opentui/solid": ">=0.1.104" }, "peerDependenciesMeta": { "@opentui/core": { From fad26187574c606096a5622c3731f6eadb91cea1 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 20:45:18 +0000 Subject: [PATCH 225/426] 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 3562cf702..8fa0e2787 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-I5qF+zaPXIXlPWjI54XlsZ42E5EgUWbo74/ClaoJnNY=", - "aarch64-linux": "sha256-2yqFliYylXy3Lnf95nO/enefCrmP8GOfqKjnstdaMu4=", - "aarch64-darwin": "sha256-/Rk2Q6bPxOzD24HPQOe/EXlcEb7eGLGHi3P3iSqHaTU=", - "x86_64-darwin": "sha256-sKyoY9Z7858S0KG/mDhuGZLR2P7AdK+zy0xUVEJ3bt0=" + "x86_64-linux": "sha256-zQOuZkuOLm9xV5YzfcQiWSzvussXVw/dRPw4HhuYsdc=", + "aarch64-linux": "sha256-J8Ak9rCCkKlBj0KhdUOklglpR3ntRP2VjtdmEJox6Ro=", + "aarch64-darwin": "sha256-r4F6S9CfGR6qxSaSqu9AIjWCEKd/PuT1Wprcbzk0IwY=", + "x86_64-darwin": "sha256-I/ztb7VnLbJYzAxf2egN0+YsJcP5yix3tIoHtiuRUXE=" } } From af3998c8a60dc00f27c94a410c4f508e5b0891a7 Mon Sep 17 00:00:00 2001 From: opencode Date: Sun, 26 Apr 2026 21:01:16 +0000 Subject: [PATCH 226/426] sync release versions for v1.14.26 --- bun.lock | 32 +++++++++++++------------- 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/core/package.json | 2 +- packages/desktop-electron/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/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 19 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index 8a3684c0b..c5eff531f 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -83,7 +83,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -117,7 +117,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -144,7 +144,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -168,7 +168,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -192,7 +192,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "1.14.25", + "version": "1.14.26", "bin": { "opencode": "./bin/opencode", }, @@ -226,7 +226,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -259,7 +259,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -303,7 +303,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -332,7 +332,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -348,7 +348,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.14.25", + "version": "1.14.26", "bin": { "opencode": "./bin/opencode", }, @@ -491,7 +491,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -526,7 +526,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "cross-spawn": "catalog:", }, @@ -541,7 +541,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -576,7 +576,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", @@ -625,7 +625,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.14.25", + "version": "1.14.26", "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 f9d8150ba..d5349a4a4 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.14.25", + "version": "1.14.26", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 5abd19256..2572ba175 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.14.25", + "version": "1.14.26", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 31f4f9a0a..67f6dad75 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.14.25", + "version": "1.14.26", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 4f30ea99b..2f02393dd 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.14.25", + "version": "1.14.26", "$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 39556c7d3..77603c4cb 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.14.25", + "version": "1.14.26", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/core/package.json b/packages/core/package.json index 62d56908c..d69f1b575 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.25", + "version": "1.14.26", "name": "@opencode-ai/core", "type": "module", "license": "MIT", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index f382ad16d..69bef5dff 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.14.25", + "version": "1.14.26", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 242bd1971..8baca7e90 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.14.25", + "version": "1.14.26", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index a5a2997b9..93c3f15f1 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.14.25", + "version": "1.14.26", "private": true, "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 6deaee220..fee6803c4 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.14.25" +version = "1.14.26" 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.14.25/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.26/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.26/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.26/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.14.25/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.26/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.14.25/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.26/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 6c1428ad5..dc4ff1901 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.14.25", + "version": "1.14.26", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index df0043b06..72032b3e1 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.14.25", + "version": "1.14.26", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index af13724cd..edd615a25 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.14.25", + "version": "1.14.26", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 3f6ffa7a5..12045fba2 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.14.25", + "version": "1.14.26", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/slack/package.json b/packages/slack/package.json index 9ab39fad7..0e2db047d 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.14.25", + "version": "1.14.26", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index da7a0f673..e5e8bad61 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.14.25", + "version": "1.14.26", "type": "module", "license": "MIT", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index ce4a80436..8c26f31d4 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.14.25", + "version": "1.14.26", "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 73c6cc9fb..df4a484cb 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.14.25", + "version": "1.14.26", "publisher": "sst-dev", "repository": { "type": "git", From c68907ece239819a6fdd31f6b2463fb69fbc5b37 Mon Sep 17 00:00:00 2001 From: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Date: Mon, 27 Apr 2026 00:27:02 +0200 Subject: [PATCH 227/426] fix(tui): update toast duration handling to use default value (#23395) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/cli/cmd/tui/event.ts | 8 ++++++-- packages/opencode/src/cli/cmd/tui/ui/toast.tsx | 13 ++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/event.ts b/packages/opencode/src/cli/cmd/tui/event.ts index ab85b1e64..1c764c12f 100644 --- a/packages/opencode/src/cli/cmd/tui/event.ts +++ b/packages/opencode/src/cli/cmd/tui/event.ts @@ -1,6 +1,8 @@ import { BusEvent } from "@/bus/bus-event" import { SessionID } from "@/session/schema" -import { Schema } from "effect" +import { Effect, Schema } from "effect" + +const DEFAULT_TOAST_DURATION = 5000 export const TuiEvent = { PromptAppend: BusEvent.define("tui.prompt.append", Schema.Struct({ text: Schema.String })), @@ -36,7 +38,9 @@ export const TuiEvent = { title: Schema.optional(Schema.String), message: Schema.String, variant: Schema.Literals(["info", "success", "warning", "error"]), - duration: Schema.optional(Schema.Number).annotate({ description: "Duration in milliseconds" }), + duration: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_TOAST_DURATION))).annotate({ + description: "Duration in milliseconds", + }), }), ), SessionSelect: BusEvent.define( diff --git a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx index 69674ba7c..d15fb3920 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/toast.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/toast.tsx @@ -5,10 +5,13 @@ import { useTerminalDimensions } from "@opentui/solid" import { SplitBorder } from "../component/border" import { TextAttributes } from "@opentui/core" import { Schema } from "effect" -import { type TuiEvent } from "../event" +import { TuiEvent } from "../event" +type ToastInput = Schema.Codec.Encoded export type ToastOptions = Schema.Schema.Type +const decodeToastOptions = Schema.decodeUnknownSync(TuiEvent.ToastShow.properties) + export function Toast() { const toast = useToast() const { theme } = useTheme() @@ -55,13 +58,13 @@ function init() { let timeoutHandle: NodeJS.Timeout | null = null const toast = { - show(options: ToastOptions) { - const { duration, ...currentToast } = options - setStore("currentToast", currentToast) + show(options: ToastInput) { + const toastOptions = decodeToastOptions(options) + setStore("currentToast", toastOptions) if (timeoutHandle) clearTimeout(timeoutHandle) timeoutHandle = setTimeout(() => { setStore("currentToast", null) - }, duration).unref() + }, toastOptions.duration).unref() }, error: (err: any) => { if (err instanceof Error) From e9071b0a8088c1679c730c25fdb59c25db271ea5 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 26 Apr 2026 19:33:18 -0400 Subject: [PATCH 228/426] tui: remove excessive debug logging from workspace creation flow to reduce terminal output noise --- .../tui/component/dialog-workspace-create.tsx | 101 ++---------------- packages/opencode/src/server/proxy.ts | 2 +- 2 files changed, 7 insertions(+), 96 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx index 899ab42ee..009bb74d2 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx @@ -6,8 +6,7 @@ import { useSync } from "@tui/context/sync" import { useProject } from "@tui/context/project" import { createMemo, createSignal, onMount } from "solid-js" import { setTimeout as sleep } from "node:timers/promises" -import { errorData, errorMessage } from "@/util/error" -import * as Log from "@opencode-ai/core/util/log" +import { errorMessage } from "@/util/error" import { useSDK } from "../context/sdk" import { useToast } from "../ui/toast" @@ -17,8 +16,6 @@ type Adaptor = { description: string } -const log = Log.Default.clone().tag("service", "tui-workspace") - function scoped(sdk: ReturnType, sync: ReturnType, workspaceID: string) { return createOpencodeClient({ baseUrl: sdk.url, @@ -37,18 +34,9 @@ export async function openWorkspaceSession(input: { workspaceID: string }) { const client = scoped(input.sdk, input.sync, input.workspaceID) - log.info("workspace session create requested", { - workspaceID: input.workspaceID, - }) while (true) { - const result = await client.session.create({ workspace: input.workspaceID }).catch((err) => { - log.error("workspace session create request failed", { - workspaceID: input.workspaceID, - error: errorData(err), - }) - return undefined - }) + const result = await client.session.create({ workspace: input.workspaceID }).catch(() => undefined) if (!result) { input.toast.show({ message: "Failed to create workspace session", @@ -56,24 +44,11 @@ export async function openWorkspaceSession(input: { }) return } - log.info("workspace session create response", { - workspaceID: input.workspaceID, - status: result.response?.status, - sessionID: result.data?.id, - }) if (result.response?.status && result.response.status >= 500 && result.response.status < 600) { - log.warn("workspace session create retrying after server error", { - workspaceID: input.workspaceID, - status: result.response.status, - }) await sleep(1000) continue } if (!result.data) { - log.error("workspace session create returned no data", { - workspaceID: input.workspaceID, - status: result.response?.status, - }) input.toast.show({ message: "Failed to create workspace session", variant: "error", @@ -85,10 +60,6 @@ export async function openWorkspaceSession(input: { type: "session", sessionID: result.data.id, }) - log.info("workspace session create complete", { - workspaceID: input.workspaceID, - sessionID: result.data.id, - }) input.dialog.clear() return } @@ -104,27 +75,10 @@ export async function restoreWorkspaceSession(input: { sessionID: string done?: () => void }) { - log.info("session restore requested", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - }) const result = await input.sdk.client.experimental.workspace .sessionRestore({ id: input.workspaceID, sessionID: input.sessionID }) - .catch((err) => { - log.error("session restore request failed", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - error: errorData(err), - }) - return undefined - }) + .catch(() => undefined) if (!result?.data) { - log.error("session restore failed", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - status: result?.response?.status, - error: result?.error ? errorData(result.error) : undefined, - }) input.toast.show({ message: `Failed to restore session: ${errorMessage(result?.error ?? "no response")}`, variant: "error", @@ -132,33 +86,11 @@ export async function restoreWorkspaceSession(input: { return } - log.info("session restore response", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - status: result.response?.status, - total: result.data.total, - }) - input.project.workspace.set(input.workspaceID) - try { - await input.sync.bootstrap({ fatal: false }) - } catch (e) {} + await input.sync.bootstrap({ fatal: false }).catch(() => undefined) - await Promise.all([input.project.workspace.sync(), input.sync.session.sync(input.sessionID)]).catch((err) => { - log.error("session restore refresh failed", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - error: errorData(err), - }) - throw err - }) - - log.info("session restore complete", { - workspaceID: input.workspaceID, - sessionID: input.sessionID, - total: result.data.total, - }) + await Promise.all([input.project.workspace.sync(), input.sync.session.sync(input.sessionID)]) input.toast.show({ message: "Session restored into the new workspace", @@ -230,47 +162,26 @@ export function DialogWorkspaceCreate(props: { onSelect: (workspaceID: string) = const create = async (type: string) => { if (creating()) return setCreating(type) - log.info("workspace create requested", { - type, - }) - const result = await sdk.client.experimental.workspace.create({ type, branch: null }).catch((err) => { + const result = await sdk.client.experimental.workspace.create({ type, branch: null }).catch(() => { toast.show({ message: "Creating workspace failed", variant: "error", }) - log.error("workspace create request failed", { - type, - error: errorData(err), - }) return undefined }) const workspace = result?.data if (!workspace) { setCreating(undefined) - log.error("workspace create failed", { - type, - status: result?.response.status, - error: result?.error ? errorData(result.error) : undefined, - }) toast.show({ message: `Failed to create workspace: ${errorMessage(result?.error ?? "no response")}`, variant: "error", }) return } - log.info("workspace create response", { - type, - workspaceID: workspace.id, - status: result.response?.status, - }) await project.workspace.sync() - log.info("workspace create synced", { - type, - workspaceID: workspace.id, - }) await props.onSelect(workspace.id) setCreating(undefined) } diff --git a/packages/opencode/src/server/proxy.ts b/packages/opencode/src/server/proxy.ts index 19a623cb0..4d9bcd117 100644 --- a/packages/opencode/src/server/proxy.ts +++ b/packages/opencode/src/server/proxy.ts @@ -101,7 +101,7 @@ const app = (upgrade: UpgradeWebSocket) => }), ) -const log = Log.Default.clone().tag("service", "server-proxy") +const log = Log.create({ service: "server-proxy" }) export async function http(url: string | URL, extra: HeadersInit | undefined, req: Request, workspaceID: WorkspaceID) { if (!Workspace.isSyncing(workspaceID)) { From 58244eb6879abacfa31cd058d3dee32151012f74 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sun, 26 Apr 2026 19:55:13 -0400 Subject: [PATCH 229/426] feat(httpapi): bridge event stream (#24518) --- packages/opencode/specs/effect/http-api.md | 4 +- .../server/routes/instance/httpapi/event.ts | 46 ++++++++++++++++ .../server/routes/instance/httpapi/server.ts | 2 + .../src/server/routes/instance/index.ts | 2 + .../test/server/httpapi-event.test.ts | 52 +++++++++++++++++++ 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 packages/opencode/src/server/routes/instance/httpapi/event.ts create mode 100644 packages/opencode/test/server/httpapi-event.test.ts diff --git a/packages/opencode/specs/effect/http-api.md b/packages/opencode/specs/effect/http-api.md index 5f16ef197..6536ac947 100644 --- a/packages/opencode/specs/effect/http-api.md +++ b/packages/opencode/specs/effect/http-api.md @@ -184,7 +184,7 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho | experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list | | `session` | `bridged` | read, lifecycle, prompt, message/part mutations, revert, permission reply | | `sync` | `bridged` | start/replay/history | -| `event` | `special` | SSE | +| `event` | `bridged` | SSE via raw Effect HTTP | | `pty` | `special` | websocket | | `tui` | `special` | UI bridge | @@ -316,7 +316,7 @@ This checklist tracks bridge parity only. Checked routes are available through t ### Event Routes -- [ ] `GET /event` - SSE event stream; replace with raw Effect HTTP, not `HttpApi`. +- [x] `GET /event` - SSE event stream via raw Effect HTTP. ### PTY Routes diff --git a/packages/opencode/src/server/routes/instance/httpapi/event.ts b/packages/opencode/src/server/routes/instance/httpapi/event.ts new file mode 100644 index 000000000..dc5fa9c53 --- /dev/null +++ b/packages/opencode/src/server/routes/instance/httpapi/event.ts @@ -0,0 +1,46 @@ +import { Bus } from "@/bus" +import { Log } from "@/util" +import { Effect } from "effect" +import * as Stream from "effect/Stream" +import { HttpRouter, HttpServerResponse } from "effect/unstable/http" + +const log = Log.create({ service: "server" }) + +export const EventPaths = { + event: "/event", +} as const + +function eventData(data: unknown) { + return `data: ${JSON.stringify(data)}\n\n` +} + +export const eventRoute = HttpRouter.add( + "GET", + EventPaths.event, + Effect.gen(function* () { + const bus = yield* Bus.Service + const events = bus.subscribeAll().pipe(Stream.takeUntil((event) => event.type === Bus.InstanceDisposed.type)) + const heartbeat = Stream.tick("10 seconds").pipe( + Stream.drop(1), + Stream.map(() => ({ type: "server.heartbeat", properties: {} })), + ) + + log.info("event connected") + return HttpServerResponse.stream( + Stream.make({ type: "server.connected", properties: {} }).pipe( + Stream.concat(events.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }))), + Stream.map(eventData), + Stream.encodeText, + Stream.ensuring(Effect.sync(() => log.info("event disconnected"))), + ), + { + contentType: "text/event-stream", + headers: { + "Cache-Control": "no-cache, no-transform", + "X-Accel-Buffering": "no", + "X-Content-Type-Options": "nosniff", + }, + }, + ) + }).pipe(Effect.provide(Bus.layer)), +) diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index adc70a43b..1e8b23f55 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -10,6 +10,7 @@ import { lazy } from "@/util/lazy" import { Filesystem } from "@/util" import { authorizationLayer } from "./auth" import { ConfigApi, configHandlers } from "./config" +import { eventRoute } from "./event" import { FileApi, fileHandlers } from "./file" import { ExperimentalApi, experimentalHandlers } from "./experimental" import { InstanceApi, instanceHandlers } from "./instance" @@ -66,6 +67,7 @@ const instance = HttpRouter.middleware()( ).layer export const routes = Layer.mergeAll( + eventRoute, HttpApiBuilder.layer(ConfigApi).pipe(Layer.provide(configHandlers)), HttpApiBuilder.layer(ExperimentalApi).pipe(Layer.provide(experimentalHandlers)), HttpApiBuilder.layer(FileApi).pipe(Layer.provide(fileHandlers)), diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 4c0503af5..c2e89c14e 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -16,6 +16,7 @@ import { QuestionRoutes } from "./question" import { PermissionRoutes } from "./permission" import { Flag } from "@opencode-ai/core/flag/flag" import { ExperimentalHttpApiServer } from "./httpapi/server" +import { EventPaths } from "./httpapi/event" import { ExperimentalPaths } from "./httpapi/experimental" import { FilePaths } from "./httpapi/file" import { InstancePaths } from "./httpapi/instance" @@ -41,6 +42,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) { const handler = ExperimentalHttpApiServer.webHandler().handler const context = Context.empty() as Context.Context + app.get(EventPaths.event, (c) => handler(c.req.raw, context)) app.get("/question", (c) => handler(c.req.raw, context)) app.post("/question/:requestID/reply", (c) => handler(c.req.raw, context)) app.post("/question/:requestID/reject", (c) => handler(c.req.raw, context)) diff --git a/packages/opencode/test/server/httpapi-event.test.ts b/packages/opencode/test/server/httpapi-event.test.ts new file mode 100644 index 000000000..42d2f8036 --- /dev/null +++ b/packages/opencode/test/server/httpapi-event.test.ts @@ -0,0 +1,52 @@ +import { afterEach, describe, expect, test } from "bun:test" +import type { UpgradeWebSocket } from "hono/ws" +import { Flag } from "@opencode-ai/core/flag/flag" +import { Instance } from "../../src/project/instance" +import { InstanceRoutes } from "../../src/server/routes/instance" +import { EventPaths } from "../../src/server/routes/instance/httpapi/event" +import { Log } from "../../src/util" +import { resetDatabase } from "../fixture/db" +import { tmpdir } from "../fixture/fixture" + +void Log.init({ print: false }) + +const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI +const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket + +function app() { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true + return InstanceRoutes(websocket) +} + +async function readFirstChunk(response: Response) { + if (!response.body) throw new Error("missing response body") + const reader = response.body.getReader() + const result = await Promise.race([ + reader.read(), + new Promise((_, reject) => setTimeout(() => reject(new Error("timed out waiting for event")), 5_000)), + ]) + await reader.cancel() + return new TextDecoder().decode(result.value) +} + +afterEach(async () => { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original + await Instance.disposeAll() + await resetDatabase() +}) + +describe("event HttpApi bridge", () => { + test("serves event stream through experimental Effect route", async () => { + await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } }) + const response = await app().request(EventPaths.event, { headers: { "x-opencode-directory": tmp.path } }) + + expect(response.status).toBe(200) + expect(response.headers.get("content-type")).toContain("text/event-stream") + expect(response.headers.get("cache-control")).toBe("no-cache, no-transform") + expect(response.headers.get("x-accel-buffering")).toBe("no") + expect(response.headers.get("x-content-type-options")).toBe("nosniff") + expect(await readFirstChunk(response)).toContain( + 'data: {"type":"server.connected","properties":{}}\n\n', + ) + }) +}) From c4d8a8183e6c2d15831767f1b898a8d0ed0297b9 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 26 Apr 2026 23:56:15 +0000 Subject: [PATCH 230/426] chore: generate --- packages/opencode/test/server/httpapi-event.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/opencode/test/server/httpapi-event.test.ts b/packages/opencode/test/server/httpapi-event.test.ts index 42d2f8036..53bb1edb2 100644 --- a/packages/opencode/test/server/httpapi-event.test.ts +++ b/packages/opencode/test/server/httpapi-event.test.ts @@ -45,8 +45,6 @@ describe("event HttpApi bridge", () => { expect(response.headers.get("cache-control")).toBe("no-cache, no-transform") expect(response.headers.get("x-accel-buffering")).toBe("no") expect(response.headers.get("x-content-type-options")).toBe("nosniff") - expect(await readFirstChunk(response)).toContain( - 'data: {"type":"server.connected","properties":{}}\n\n', - ) + expect(await readFirstChunk(response)).toContain('data: {"type":"server.connected","properties":{}}\n\n') }) }) From 141f33d24bdc059aa26bd1e32c9416ac3aed36e1 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Mon, 27 Apr 2026 10:54:55 +1000 Subject: [PATCH 231/426] feat: configurable shell selection + desktop settings UI (#20602) --- .../app/src/components/settings-general.tsx | 187 ++++++++++++------ .../app/src/context/global-sync/bootstrap.ts | 7 +- packages/app/src/i18n/en.ts | 5 + packages/opencode/src/config/config.ts | 20 +- packages/opencode/src/pty/index.ts | 21 +- .../src/server/routes/instance/pty.ts | 27 +++ packages/opencode/src/session/prompt.ts | 53 +---- packages/opencode/src/shell/shell.ts | 145 ++++++++++++-- packages/opencode/src/tool/bash.ts | 17 +- packages/opencode/test/config/config.test.ts | 102 ++++++++++ packages/opencode/test/pty/pty-shell.test.ts | 35 ++++ packages/opencode/test/session/prompt.test.ts | 67 +++++++ packages/opencode/test/shell/shell.test.ts | 28 ++- packages/opencode/test/tool/bash.test.ts | 29 +++ packages/sdk/js/src/v2/gen/sdk.gen.ts | 31 +++ packages/sdk/js/src/v2/gen/types.gen.ts | 27 +++ packages/sdk/openapi.json | 60 ++++++ packages/web/src/content/docs/config.mdx | 15 ++ 18 files changed, 720 insertions(+), 156 deletions(-) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index f38442379..8060ae94d 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -11,7 +11,9 @@ import { showToast } from "@opencode-ai/ui/toast" import { useParams } from "@solidjs/router" import { useLanguage } from "@/context/language" import { usePermission } from "@/context/permission" -import { usePlatform } from "@/context/platform" +import { usePlatform, type DisplayBackend } from "@/context/platform" +import { useGlobalSync } from "@/context/global-sync" +import { useGlobalSDK } from "@/context/global-sdk" import { monoDefault, monoFontFamily, @@ -40,6 +42,18 @@ type ThemeOption = { name: string } +type ShellOption = { + path: string + name: string + acceptable: boolean +} + +type ShellSelectOption = { + id: string + value: string + label: string +} + // To prevent audio from overlapping/playing very quickly when navigating the settings menus, // delay the playback by 100ms during quick selection changes and pause existing sounds. const stopDemoSound = () => { @@ -75,10 +89,6 @@ export const SettingsGeneral: Component = () => { const params = useParams() const settings = useSettings() - onMount(() => { - void theme.loadThemes() - }) - const [store, setStore] = createStore({ checking: false, }) @@ -165,6 +175,70 @@ export const SettingsGeneral: Component = () => { const themeOptions = createMemo(() => theme.ids().map((id) => ({ id, name: theme.name(id) }))) + const globalSync = useGlobalSync() + const globalSdk = useGlobalSDK() + + const [shells] = createResource( + () => + globalSdk.client.pty + .shells() + .then((res) => res.data ?? []) + .catch(() => [] as ShellOption[]), + { initialValue: [] as ShellOption[] }, + ) + + const [displayBackend, { refetch: refetchDisplayBackend }] = createResource( + () => (linux() && platform.getDisplayBackend ? true : false), + () => Promise.resolve(platform.getDisplayBackend?.() ?? null).catch(() => null as DisplayBackend | null), + { initialValue: null as DisplayBackend | null }, + ) + + onMount(() => { + void theme.loadThemes() + }) + + const autoOption = { id: "auto", value: "", label: language.t("settings.general.row.shell.autoDefault") } + const currentShell = createMemo(() => globalSync.data.config.shell ?? "") + + const shellOptions = createMemo(() => { + const list = shells.latest + const current = globalSync.data.config.shell + + const nameCounts = new Map() + for (const s of list) { + nameCounts.set(s.name, (nameCounts.get(s.name) || 0) + 1) + } + + const options = [ + autoOption, + ...list.map((s) => { + const ambiguousName = (nameCounts.get(s.name) || 0) > 1 + const text = ambiguousName ? s.path : s.name + const label = s.acceptable ? text : `${text} (${language.t("settings.general.row.shell.terminalOnly")})` + return { + id: s.path, + // Prefer name over path - "bash" is much cleaner than the explicit full route even when it may change due to PATH. + value: ambiguousName ? s.path : s.name, + label, + } + }), + ] + + if (current && !options.some((o) => o.value === current)) { + options.push({ id: current, value: current, label: current }) + } + + return options + }) + + const onDisplayBackendChange = (checked: boolean) => { + const update = platform.setDisplayBackend?.(checked ? "wayland" : "auto") + if (!update) return + void update.finally(() => { + void refetchDisplayBackend() + }) + } + const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [ { value: "system", label: language.t("theme.scheme.system") }, { value: "light", label: language.t("theme.scheme.light") }, @@ -243,6 +317,27 @@ export const SettingsGeneral: Component = () => {

    + +