From 44dd4b803bd3e322745b1f40b736a82767162816 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 1 Aug 2026 14:52:49 -0300 Subject: [PATCH 001/129] fix(app): restore current session fork controls Route the V2 session UI through current-session data so durable queueing, sticky models, context usage, and scroll state survive the upstream merge. Cover the restored paths with deterministic unit and browser regression tests. --- .../file-browser-sidebar-tab-switch.spec.ts | 7 +- .../e2e/regression/review-open-file.spec.ts | 44 +- packages/app/e2e/utils/mock-server.ts | 189 +++++- packages/app/e2e/utils/sse-transport.ts | 12 +- packages/app/e2e/utils/waits.ts | 2 +- packages/app/src/components/file-tree-v2.tsx | 21 - .../app/src/components/prompt-input-v2.tsx | 21 +- packages/app/src/components/prompt-input.tsx | 57 +- .../src/components/prompt-input/contracts.ts | 5 +- .../components/prompt-input/submit.test.ts | 611 ++++++++++++++---- .../app/src/components/prompt-input/submit.ts | 346 ++++------ .../session/session-context-tab.tsx | 5 +- .../src/context/global-sync/bootstrap.test.ts | 10 +- packages/app/src/pages/session.tsx | 228 +++---- .../src/pages/session/session-side-panel.tsx | 20 +- .../session/v2/session-file-browser-tab.tsx | 25 +- packages/app/src/utils/server.ts | 1 + .../src/v2/components/prompt-input/index.tsx | 30 +- .../v2/components/prompt-input/interaction.ts | 9 + 19 files changed, 1121 insertions(+), 522 deletions(-) diff --git a/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts b/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts index 5b6625b14f79..af64d2b3057a 100644 --- a/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts +++ b/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts @@ -35,13 +35,16 @@ test("keeps the file-browser sidebar mounted when switching file tabs", async ({ await expect(panel.getByText("contents:file-00.ts", { exact: true })).toBeVisible() const viewport = panel.locator('[data-slot="session-review-v2-sidebar-tree"] .scroll-view__viewport') - await viewport.hover() - await page.mouse.wheel(0, 100_000) + await viewport.evaluate((element) => { + element.scrollTop = element.scrollHeight + element.dispatchEvent(new Event("scroll")) + }) await expect .poll(() => viewport.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop)) .toBeLessThanOrEqual(1) const scrolled = await viewport.evaluate((element) => element.scrollTop) expect(scrolled).toBeGreaterThan(0) + await expect(panel.getByRole("button", { name: "file-79.ts" })).toBeVisible() await writeProbe(page) await panel.getByRole("button", { name: "file-79.ts" }).click() diff --git a/packages/app/e2e/regression/review-open-file.spec.ts b/packages/app/e2e/regression/review-open-file.spec.ts index 1fc9f765a52a..ab5c42ff6179 100644 --- a/packages/app/e2e/regression/review-open-file.spec.ts +++ b/packages/app/e2e/regression/review-open-file.spec.ts @@ -1,19 +1,20 @@ import { base64Encode } from "@opencode-ai/core/util/encode" import { expect, test } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" -import { expectSessionTitle } from "../utils/waits" const directory = "C:/OpenCode/ReviewOpenFile" const projectID = "proj_review_open_file" const sessionID = "ses_review_open_file" const title = "Review open file" const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` +const sessionKey = `local\u0000${base64Encode(directory)}/${sessionID}` test.use({ viewport: { width: 1440, height: 900 } }) test("opens and searches project files inline", async ({ page }) => { const searches: { query: string; dirs?: string; limit?: number }[] = [] await mockOpenCodeServer(page, { + protocol: "v2", directory, project: { id: projectID, @@ -58,10 +59,32 @@ test("opens and searches project files inline", async ({ page }) => { searches.push(input) return input.query === "nested" ? ["src/nested.ts"] : [] }, - currentPageMessages: () => ({ items: [], throughSeq: 0 }), + pageMessages: () => ({ + items: [], + }), + currentPageMessages: () => ({ + items: [ + { + id: "msg_context_user", + type: "user", + time: { created: 1700000000000 }, + text: "Show context usage", + }, + { + id: "msg_context_assistant", + type: "assistant", + agent: "build", + model: { providerID: "opencode", id: "test" }, + time: { created: 1700000001000 }, + tokens: { input: 120, output: 30, reasoning: 10, cache: { read: 20, write: 5 } }, + content: [{ id: "prt_context_assistant", type: "text", text: "Context usage is available." }], + }, + ], + throughSeq: 2, + }), }) await page.addInitScript( - ({ directory, server, sessionID }) => { + ({ directory, server, sessionID, sessionKey }) => { localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) localStorage.setItem( "opencode.global.dat:server", @@ -72,7 +95,10 @@ test("opens and searches project files inline", async ({ page }) => { ) localStorage.setItem( "opencode.global.dat:layout", - JSON.stringify({ review: { diffStyle: "split", panelOpened: true } }), + JSON.stringify({ + review: { diffStyle: "split", panelOpened: true }, + sessionTabs: { [sessionKey]: { all: ["context"], active: "context" } }, + }), ) localStorage.setItem( "opencode.global.dat:review-panel-v2", @@ -83,23 +109,23 @@ test("opens and searches project files inline", async ({ page }) => { JSON.stringify([{ type: "session", server, sessionId: sessionID }]), ) }, - { directory, server, sessionID }, + { directory, server, sessionID, sessionKey }, ) await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`) - await expectSessionTitle(page, title) + await expect(page.getByRole("link", { name: title }).first()).toBeVisible() const panel = page.locator("#review-panel") const sidebar = panel.locator('[data-slot="session-review-v2-sidebar"]') const sidebarToggle = panel.getByRole("button", { name: "Toggle file tree" }) - const contextButton = page.getByRole("button", { name: "View context usage" }) - await contextButton.click() await expect(panel.getByRole("tab", { name: "Context" })).toHaveAttribute("data-selected", "") + await expect(panel.getByText("assistant • msg_context_assistant", { exact: true })).toBeVisible() + await expect(panel.getByText("185", { exact: true })).toBeVisible() await panel.getByRole("button", { name: "Open file" }).click() await expect(panel.getByRole("tab", { name: "Open file" })).toHaveAttribute("data-selected", "") await expect(sidebarToggle).toBeDisabled() await expect(sidebar).toBeVisible() - await contextButton.click() + await panel.getByRole("tab", { name: "Context" }).click() await expect(panel.getByRole("tab", { name: "Context" })).toHaveAttribute("data-selected", "") await expect(sidebar).toBeHidden() await panel.getByRole("button", { name: "Open file" }).click() diff --git a/packages/app/e2e/utils/mock-server.ts b/packages/app/e2e/utils/mock-server.ts index 76987421b607..702ffb4de1f3 100644 --- a/packages/app/e2e/utils/mock-server.ts +++ b/packages/app/e2e/utils/mock-server.ts @@ -12,14 +12,33 @@ export interface MockServerConfig { directory: string project: unknown sessions: ({ id: string } & Record)[] - pageMessages: (sessionId: string, limit: number, before?: string) => { items: unknown[]; cursor?: string } + createSession?: () => { id: string } & Record + onPrompt?: (input: { sessionID: string; body: unknown }) => void + onPromptAsync?: (input: { sessionID: string; body: unknown }) => void + agents?: unknown[] + pageMessages?: (sessionId: string, limit: number, before?: string) => { items: unknown[]; cursor?: string } + currentPageMessages?: ( + sessionID: string, + limit: number, + cursor?: string, + ) => { + items: unknown[] + cursor?: { previous?: string; next?: string } + throughSeq: number + } vcsDiff?: unknown[] + status?: Record + queue?: Record + queueDetails?: Record> + onQueueSend?: (input: { sessionID: string; queueID: string; raw: string | null; body: unknown }) => void + onQueueUpdate?: (input: { sessionID: string; queueID: string; raw: string | null; body: unknown }) => void messageDelay?: number beforeMessagesResponse?: (input: { sessionID: string; before?: string }) => Promise onMessages?: (input: { sessionID: string; before?: string; phase: "start" | "end" }) => void message?: (sessionID: string, messageID: string) => unknown onMessage?: (input: { sessionID: string; messageID: string }) => void events?: () => unknown[] + currentEvents?: (input: { sessionID: string; after?: number }) => unknown[] eventRetry?: number todos?: (sessionID: string) => unknown[] permissions?: unknown[] | (() => unknown[]) @@ -43,7 +62,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { }, "/project": [config.project], "/project/current": config.project, - "/agent": [{ name: "build", mode: "primary" }], + "/agent": config.agents ?? [{ name: "build", mode: "primary" }], "/vcs": { branch: "main", default_branch: "main" }, "/session": config.sessions, } @@ -76,6 +95,10 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { return config.protocol === "v2" ? json(route, {}, undefined, 404) : json(route, { healthy: true }) if (path === "/api/health" && config.protocol === "v2") return json(route, { healthy: true, version: "2.0.0", pid: 1 }) + if (path === "/api/session" && route.request().method() === "POST") { + const created = config.createSession?.() + if (created) return json(route, { data: currentSession(created, config.directory) }) + } if (path === "/experimental/capabilities") return json(route, { backgroundSubagents: true }) if (path === "/provider") return json(route, typeof config.provider === "function" ? config.provider() : config.provider) @@ -120,6 +143,32 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { }, data: [], }) + if (path === "/api/provider") return json(route, { location: location(config), data: currentCatalog(config).providers }) + if (path === "/api/model") return json(route, { location: location(config), data: currentCatalog(config).models }) + if (path === "/api/model/default") return json(route, { location: location(config), data: currentCatalog(config).default }) + if (path === "/api/fs/list" && config.fileList) { + const files = await config.fileList(url.searchParams.get("path") ?? "") + return json(route, { + location: location(config), + data: files instanceof Array + ? files.map((entry) => { + const item = entry as { path: string; type: "file" | "directory" } + return { path: item.path, type: item.type } + }) + : [], + }) + } + if (path === "/api/fs/find" && config.findFiles) { + const files = await config.findFiles({ + query: url.searchParams.get("query") ?? "", + dirs: url.searchParams.get("type") === "directory" ? "true" : "false", + limit: url.searchParams.has("limit") ? Number(url.searchParams.get("limit")) : undefined, + }) + return json(route, { + location: location(config), + data: files instanceof Array ? files.map((path) => ({ path, type: "file" })) : [], + }) + } if (path === "/api/agent") return json(route, { location: location(config), @@ -208,7 +257,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { }) } if (path === "/api/session/active") { - const statuses = (config.sessionStatus ?? {}) as Record + const statuses = (config.sessionStatus ?? config.status ?? {}) as Record return json(route, { data: Object.fromEntries( Object.entries(statuses).flatMap(([id, status]) => @@ -217,6 +266,42 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { ), }) } + const queueMatch = path.match(/^\/api\/session\/([^/]+)\/queue$/) + if (queueMatch && route.request().method() === "GET") + return json(route, { + data: (config.queue?.[queueMatch[1]] ?? []).map((item, position) => queuedInput(queueMatch[1]!, item, position)), + }) + if (queueMatch && route.request().method() === "POST") return json(route, { data: { id: "msg_queue_mock" } }) + const queueSendMatch = path.match(/^\/api\/session\/([^/]+)\/queue\/([^/]+)\/send$/) + if (queueSendMatch && route.request().method() === "POST") { + const raw = route.request().postData() + config.onQueueSend?.({ + sessionID: queueSendMatch[1]!, + queueID: queueSendMatch[2]!, + raw, + body: raw ? JSON.parse(raw) : undefined, + }) + return json(route, true) + } + const queueItemMatch = path.match(/^\/api\/session\/([^/]+)\/queue\/([^/]+)$/) + if (queueItemMatch && route.request().method() === "GET") { + const item = config.queue?.[queueItemMatch[1]!]?.find((entry) => entry.id === queueItemMatch[2]) + return json(route, { + data: + config.queueDetails?.[queueItemMatch[1]!]?.[queueItemMatch[2]!] ?? + queuedInput(queueItemMatch[1]!, item ?? { id: queueItemMatch[2]!, text: "" }, 0), + }) + } + if (queueItemMatch && route.request().method() === "PATCH") { + const raw = route.request().postData() + config.onQueueUpdate?.({ + sessionID: queueItemMatch[1]!, + queueID: queueItemMatch[2]!, + raw, + body: raw ? JSON.parse(raw) : undefined, + }) + return json(route, true) + } if (/^\/api\/session\/[^/]+\/shell$/.test(path) && route.request().method() === "POST") { return route.fulfill({ status: 204, headers: { "access-control-allow-origin": "*" } }) } @@ -252,6 +337,13 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { }) } + const currentPromptMatch = path.match(/^\/api\/session\/([^/]+)\/prompt$/) + if (currentPromptMatch && route.request().method() === "POST") { + const body = route.request().postDataJSON() + config.onPrompt?.({ sessionID: currentPromptMatch[1]!, body }) + return json(route, { data: { id: "msg_prompt_mock", sessionID: currentPromptMatch[1] } }) + } + const sessionMatch = path.match(/^\/session\/([^/]+)$/) if (sessionMatch) { const session = config.sessions.find((s) => s.id === sessionMatch[1]) @@ -277,13 +369,27 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { const currentMessagesMatch = path.match(/^\/api\/session\/([^/]+)\/message$/) if (currentMessagesMatch) { const token = url.searchParams.get("cursor") ?? undefined + if (config.currentPageMessages) { + config.onMessages?.({ sessionID: currentMessagesMatch[1]!, before: token, phase: "start" }) + await config.beforeMessagesResponse?.({ sessionID: currentMessagesMatch[1]!, before: token }) + if (config.messageDelay !== undefined) await new Promise((resolve) => setTimeout(resolve, config.messageDelay)) + const pageData = config.currentPageMessages?.( + currentMessagesMatch[1]!, + Number(url.searchParams.get("limit") ?? 100), + token, + ) ?? { items: [], throughSeq: 0 } + config.onMessages?.({ sessionID: currentMessagesMatch[1]!, before: token, phase: "end" }) + return json(route, { data: pageData.items, throughSeq: pageData.throughSeq, cursor: pageData.cursor ?? {} }) + } const before = token ? cursors.get(token) : undefined if (token && !before) return json(route, { error: "Invalid cursor" }, undefined, 400) config.onMessages?.({ sessionID: currentMessagesMatch[1], before, phase: "start" }) await config.beforeMessagesResponse?.({ sessionID: currentMessagesMatch[1]!, before }) if (config.messageDelay !== undefined) await new Promise((resolve) => setTimeout(resolve, config.messageDelay)) - const pageData = config.pageMessages(currentMessagesMatch[1], Number(url.searchParams.get("limit") ?? 50), before) config.onMessages?.({ sessionID: currentMessagesMatch[1], before, phase: "end" }) + const pageData = config.pageMessages?.(currentMessagesMatch[1], Number(url.searchParams.get("limit") ?? 50), before) ?? { + items: [], + } const cursor = pageData.cursor ? `cursor_${++nextCursor}` : undefined if (cursor) cursors.set(cursor, pageData.cursor!) return json(route, { @@ -292,6 +398,17 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { }) } + const currentEventsMatch = path.match(/^\/api\/session\/([^/]+)\/event$/) + if (currentEventsMatch) + return sse( + route, + config.currentEvents?.({ + sessionID: currentEventsMatch[1]!, + after: url.searchParams.has("after") ? Number(url.searchParams.get("after")) : undefined, + }), + config.eventRetry, + ) + const messagesMatch = path.match(/^\/session\/([^/]+)\/message$/) if (messagesMatch) { const token = url.searchParams.get("before") ?? undefined @@ -301,7 +418,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { await config.beforeMessagesResponse?.({ sessionID: messagesMatch[1]!, before }) if (config.messageDelay !== undefined) await new Promise((resolve) => setTimeout(resolve, config.messageDelay)) const limit = Number(url.searchParams.get("limit") ?? 80) - const pageData = config.pageMessages(messagesMatch[1], limit, before) + const pageData = config.pageMessages?.(messagesMatch[1], limit, before) ?? { items: [] } config.onMessages?.({ sessionID: messagesMatch[1], before, phase: "end" }) if (!pageData.cursor) return json(route, pageData.items) const cursor = `cursor_${++nextCursor}` @@ -309,6 +426,13 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { return json(route, pageData.items, { "x-next-cursor": cursor }) } + const promptAsyncMatch = path.match(/^\/session\/([^/]+)\/prompt_async$/) + if (promptAsyncMatch && route.request().method() === "POST") { + const raw = route.request().postData() + config.onPromptAsync?.({ sessionID: promptAsyncMatch[1]!, body: raw ? JSON.parse(raw) : undefined }) + return json(route, true) + } + if (url.port === targetPort && targetPort !== appPort) return json(route, {}) return route.fallback() }) @@ -321,6 +445,43 @@ function location(config: MockServerConfig) { } } +function currentCatalog(config: MockServerConfig) { + const value = typeof config.provider === "function" ? config.provider() : config.provider + if (!value || typeof value !== "object") return { providers: [], models: [], default: null } + const catalog = value as { + all?: { id?: string; name?: string; package?: string; models?: Record }[] + default?: { providerID?: string; modelID?: string } + } + const providers = catalog.all ?? [] + const models = providers.flatMap((provider) => + Object.entries(provider.models ?? {}).map(([id, model]) => ({ + id: model.id ?? id, + modelID: model.id ?? id, + providerID: provider.id ?? "", + name: model.name ?? model.id ?? id, + package: provider.package ?? "", + capabilities: { tools: true, input: ["text"], output: ["text"] }, + variants: [], + time: { released: 0 }, + cost: [], + status: "active" as const, + enabled: true, + limit: { context: 0, output: 0, ...(model.limit as Record | undefined) }, + })), + ) + return { + providers: providers.map((provider) => ({ + id: provider.id ?? "", + name: provider.name ?? provider.id ?? "", + package: provider.package ?? "", + })), + models, + default: models.find( + (model) => model.providerID === catalog.default?.providerID && model.id === catalog.default.modelID, + ) ?? null, + } +} + function currentPermission(value: unknown) { const permission = value as Record if (permission.action) return permission @@ -389,7 +550,8 @@ function currentMessage(value: unknown) { tokens: item.info.tokens, error: item.info.error, content: item.parts.flatMap((part) => { - if (part.type === "text" || part.type === "reasoning") return [{ type: part.type, text: part.text ?? "" }] + if (part.type === "text" || part.type === "reasoning") + return [{ id: part.id ?? `${item.info.id}_${part.type}`, type: part.type, text: part.text ?? "" }] if (part.type !== "tool") return [] const state = part.state as Record return [ @@ -423,6 +585,21 @@ function currentMessage(value: unknown) { } } +function queuedInput(sessionID: string, item: { id: string; text: string }, position: number) { + return { + id: item.id, + sessionID, + position, + timeCreated: position, + payload: { + version: 1, + agent: "build", + model: { providerID: "opencode", modelID: "test-model" }, + parts: [{ type: "text", text: item.text }], + }, + } +} + function json(route: Route, body: unknown, headers?: Record, status = 200) { return route.fulfill({ status, diff --git a/packages/app/e2e/utils/sse-transport.ts b/packages/app/e2e/utils/sse-transport.ts index b0e3b74c6d9a..481330764944 100644 --- a/packages/app/e2e/utils/sse-transport.ts +++ b/packages/app/e2e/utils/sse-transport.ts @@ -3,7 +3,7 @@ import type { Page } from "@playwright/test" export type SseConnectionRecord = { id: number url: string - path: "/global/event" | "/event" | "/api/event" + path: string headers: Record openedAt: number endedAt?: number @@ -57,11 +57,11 @@ type BrowserTransport = Window & { export async function installSseTransport( page: Page, - options: { server: string; retry?: number }, + options: { server: string; path?: string; retry?: number }, ): Promise> { const server = new URL(options.server).origin await page.addInitScript( - ({ server, retry }) => { + ({ server, path, retry }) => { type Connection = SseConnectionRecord & { controller: ReadableStreamDefaultController } type ProbeWindow = Window & { __visualStabilityProbe?: { startedAt: number; markers: { at: number; label: string }[] } @@ -176,7 +176,9 @@ export async function installSseTransport( const url = new URL(request.url) if ( url.origin !== server || - (url.pathname !== "/global/event" && url.pathname !== "/event" && url.pathname !== "/api/event") + (path + ? url.pathname !== path + : url.pathname !== "/global/event" && url.pathname !== "/event" && url.pathname !== "/api/event") ) return originalFetch(request) @@ -234,7 +236,7 @@ export async function installSseTransport( } Object.defineProperty(window, "fetch", { configurable: true, writable: true, value: fetch }) }, - { server, retry: options.retry }, + { server, path: options.path, retry: options.retry }, ) const command = (input: BrowserCommand) => diff --git a/packages/app/e2e/utils/waits.ts b/packages/app/e2e/utils/waits.ts index 8a47815674d2..1fb33f764d86 100644 --- a/packages/app/e2e/utils/waits.ts +++ b/packages/app/e2e/utils/waits.ts @@ -7,5 +7,5 @@ export async function expectAppVisible(locator: Locator) { } export async function expectSessionTitle(page: Page, title: string) { - await expectAppVisible(page.getByRole("heading", { name: title })) + await expectAppVisible(page.getByRole("link", { name: title }).first()) } diff --git a/packages/app/src/components/file-tree-v2.tsx b/packages/app/src/components/file-tree-v2.tsx index 26cf49d411b8..ec42bb7d776a 100644 --- a/packages/app/src/components/file-tree-v2.tsx +++ b/packages/app/src/components/file-tree-v2.tsx @@ -168,27 +168,6 @@ export default function FileTreeV2(props: { void file.tree.list("") }) - // Only scroll when the active path changes (or first appears in the tree). - // Do not re-scroll when expand/collapse reshuffles `rows()`. - let scrolledActive: string | undefined - createEffect(() => { - const path = active() - if (!path) { - scrolledActive = undefined - return - } - const index = rows().findIndex((row) => row.node.path === path) - if (index < 0) return - if (scrolledActive === path) return - scrolledActive = path - queueMicrotask(() => { - const next = rows().findIndex((row) => row.node.path === path) - if (next < 0) return - if (virtualizer.range && next >= virtualizer.range.startIndex && next <= virtualizer.range.endIndex) return - virtualizer.scrollToIndex(next, { align: "auto" }) - }) - }) - const selectFile = (node: FileTreeV2Node, action?: (file: FileNode) => void) => { action?.({ ...node, diff --git a/packages/app/src/components/prompt-input-v2.tsx b/packages/app/src/components/prompt-input-v2.tsx index 44d9d48d386b..6cf01ddd3aa5 100644 --- a/packages/app/src/components/prompt-input-v2.tsx +++ b/packages/app/src/components/prompt-input-v2.tsx @@ -6,7 +6,7 @@ import { Icon } from "@opencode-ai/ui/v2/icon" import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import type { ReferenceInfo } from "@opencode-ai/sdk/v2/client" -import { createEffect, createMemo, on, Show } from "solid-js" +import { createEffect, createMemo, createSignal, on, Show } from "solid-js" import { ModelSelectorPopoverV2 } from "@/components/dialog-select-model" import { DialogSelectModelUnpaidV2 } from "@/components/dialog-select-model-unpaid-v2" import type { PromptInputProps } from "@/components/prompt-input/contracts" @@ -191,6 +191,7 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps): if (!id) return permission.isAutoAcceptingDirectory(sdk().directory) return permission.isAutoAccepting(id, sdk().directory) }) + const [queueMode, setQueueMode] = createSignal(false) const submission = createPromptSubmit({ prompt, info, @@ -211,6 +212,11 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps): newSessionWorktree: () => props.newSessionWorktree, onNewSessionWorktreeReset: props.onNewSessionWorktreeReset, shouldQueue: props.shouldQueue, + queueMode, + resetQueueMode: () => setQueueMode(false), + editingQueueID: props.editingQueueID, + editingQueuePayload: props.editingQueuePayload, + resetEditingQueueID: props.resetEditingQueueID, onQueue: props.onQueue, onAbort: props.onAbort, onSubmit: props.onSubmit, @@ -399,6 +405,19 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps): working, onSubmit: () => void submission.handleSubmit(new Event("submit")), onStop: () => void submission.abort(), + label: () => (props.editingQueueID?.() ? language.t("common.save") : language.t("prompt.action.send")), + queue: { + visible: () => + !!props.onQueue && !!props.controls.session.id && !props.editingQueueID?.() && mode() === "normal", + active: queueMode, + label: () => (queueMode() ? language.t("prompt.action.sendDirect") : language.t("prompt.action.queue")), + keybind: ["Alt", "Enter"], + onToggle: () => setQueueMode((value) => !value), + onSubmit: () => { + setQueueMode(true) + void submission.handleSubmit(new Event("submit")).finally(() => setQueueMode(false)) + }, + }, }, }, }) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 7923e18ee2ae..39b856dd01d1 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -1197,6 +1197,7 @@ export const PromptInput: Component = (props) => { if (!id) return permission.isAutoAcceptingDirectory(sdk().directory) return permission.isAutoAccepting(id, sdk().directory) }) + const [queueMode, setQueueMode] = createSignal(false) const { abort, handleSubmit } = props.submission ?? @@ -1223,12 +1224,22 @@ export const PromptInput: Component = (props) => { newSessionWorktree: () => props.newSessionWorktree, onNewSessionWorktreeReset: props.onNewSessionWorktreeReset, shouldQueue: props.shouldQueue, + queueMode, + resetQueueMode: () => setQueueMode(false), + editingQueueID: props.editingQueueID, + editingQueuePayload: props.editingQueuePayload, + resetEditingQueueID: props.resetEditingQueueID, onQueue: props.onQueue, onAbort: props.onAbort, onSubmit: props.onSubmit, model: props.controls.model.selection, }) + const queue = (event: Event) => { + setQueueMode(true) + void Promise.resolve(handleSubmit(event)).finally(() => setQueueMode(false)) + } + const handleKeyDown = (event: KeyboardEvent) => { if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === "u") { event.preventDefault() @@ -1390,6 +1401,10 @@ export const PromptInput: Component = (props) => { ) { return } + if (event.altKey && store.mode === "normal" && props.controls.session.id && props.onQueue && !props.editingQueueID?.()) { + queue(event) + return + } void handleSubmit(event) } } @@ -1574,16 +1589,54 @@ export const PromptInput: Component = (props) => { />
+ + + {queueMode() ? language.t("prompt.action.sendDirect") : language.t("prompt.action.queue")} + + + + + } + > + setQueueMode((value) => !value)} + /> + +
diff --git a/packages/app/src/components/prompt-input/contracts.ts b/packages/app/src/components/prompt-input/contracts.ts index a38f45d9256e..b1d096e85698 100644 --- a/packages/app/src/components/prompt-input/contracts.ts +++ b/packages/app/src/components/prompt-input/contracts.ts @@ -50,8 +50,11 @@ export interface PromptInputProps { onNewSessionWorktreeReset?: () => void edit?: { id: string; prompt: Prompt; context: FollowupDraft["context"] } onEditLoaded?: () => void + editingQueueID?: () => string | undefined + editingQueuePayload?: () => FollowupDraft["queuePayload"] + resetEditingQueueID?: () => void shouldQueue?: () => boolean - onQueue?: (draft: FollowupDraft) => void + onQueue?: (draft: FollowupDraft) => Promise | void onAbort?: () => void onSubmit?: () => void } diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts index b3201b3ef68a..1250f1a1d41e 100644 --- a/packages/app/src/components/prompt-input/submit.test.ts +++ b/packages/app/src/components/prompt-input/submit.test.ts @@ -1,5 +1,6 @@ import { beforeAll, beforeEach, describe, expect, mock, test } from "bun:test" import { createStore } from "solid-js/store" +import { loadMcpQuery, loadMcpResourcesQuery } from "@/context/server-sync" import type { Prompt, PromptStore } from "@/context/prompt" import type { ModelSelection } from "@/context/local" @@ -7,11 +8,6 @@ let createPromptSubmit: typeof import("./submit").createPromptSubmit const createdClients: string[] = [] const createdSessions: string[] = [] -const sessionCreateInputs: Array<{ - agent?: string - model?: { id: string; providerID: string; variant?: string } - location?: { directory: string } -}> = [] const enabledAutoAccept: Array<{ server: string; sessionID: string; directory: string }> = [] const optimistic: Array<{ directory?: string @@ -24,15 +20,25 @@ const optimistic: Array<{ }> = [] const optimisticSeeded: boolean[] = [] const storedSessions: Record> = {} -const promoted: Array<{ directory: string; sessionID: string }> = [] -const sentShell: Array<{ sessionID: string; id?: string; command: string }> = [] +const promoted: Array<{ + directory: string + sessionID: string + selection?: { + agent?: string + model?: { providerID: string; modelID: string } + variant?: string | null + source?: "history" | "user" + } +}> = [] +const sentShell: string[] = [] +const sessionLocations: Record = {} const syncedDirectories: string[] = [] +const queuedDrafts: unknown[] = [] +const promptAsyncCalls: unknown[] = [] +const commandCalls: unknown[] = [] +const currentCommands: Array<{ name: string; template: string; description?: string }> = [] const promotedDrafts: Array<{ draftID: string; server: string; sessionId: string }> = [] -const sentPrompts: string[] = [] -const promptInputs: unknown[] = [] -const sentCommands: unknown[] = [] -const commands: Array<{ name: string }> = [] -let serverSessionSyncs = 0 +const resumedQueues: string[] = [] let params: { id?: string } = {} let search: { draftId?: string } = {} @@ -73,39 +79,26 @@ const prompt = { const clientFor = (directory: string) => { createdClients.push(directory) return { - api: { - session: { - create: async (input: (typeof sessionCreateInputs)[number]) => { - await createSessionGate - const location = input.location?.directory ?? directory - createdSessions.push(location) - sessionCreateInputs.push(input) - return { + session: { + create: async () => { + await createSessionGate + createdSessions.push(directory) + return { + data: { id: `session-${createdSessions.length}`, - projectID: "project", - agent: input.agent, - model: input.model, - cost: 0, - tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, - time: { created: 1, updated: 1 }, title: `New session ${createdSessions.length}`, - location: { directory: location }, - } - }, - prompt: async (input: unknown) => { - sentPrompts.push(directory) - promptInputs.push(input) - return { data: undefined } - }, - command: async (input: unknown) => { - sentCommands.push(input) - }, - shell: async (input: { sessionID: string; id?: string; command: string }) => { - sentShell.push(input) - }, + }, + } + }, + shell: async () => { + sentShell.push(directory) + return { data: undefined } + }, + prompt: async () => ({ data: undefined }), + promptAsync: async (input: unknown) => { + promptAsyncCalls.push(input) + return { data: undefined } }, - }, - session: { command: async () => ({ data: undefined }), abort: async () => ({ data: undefined }), }, @@ -139,6 +132,7 @@ beforeAll(async () => { mock.module("@opencode-ai/core/util/encode", () => ({ base64Encode: (value: string) => value, + base64Decode: (value: string) => value, })) mock.module("@/context/local", () => ({ @@ -151,8 +145,17 @@ beforeAll(async () => { current: () => ({ name: "agent" }), }, session: { - promote(directory: string, sessionID: string) { - promoted.push({ directory, sessionID }) + promote( + directory: string, + sessionID: string, + selection?: { + agent?: string + model?: { providerID: string; modelID: string } + variant?: string | null + source?: "history" | "user" + }, + ) { + promoted.push({ directory, sessionID, selection }) }, }, }), @@ -168,6 +171,13 @@ beforeAll(async () => { }) mock.module("@/context/server", () => ({ + ServerConnection: { + Key: { make: (value: string) => value }, + key: (conn: { type?: string; http?: { url?: string } } | string) => + typeof conn === "string" ? conn : conn.type === "sidecar" ? "sidecar" : (conn.http?.url ?? "local"), + local: (conn?: { type?: string; http?: { url?: string } }) => + !conn || conn.type === "sidecar" || conn.http?.url === "http://localhost:4096", + }, useServer: () => ({ key: "server-key" }), })) @@ -198,7 +208,6 @@ beforeAll(async () => { scope: "local", directory: "/repo/main", client: rootClient, - api: rootClient.api, url: "http://localhost:4096", createClient(opts: any) { return clientFor(opts.directory) @@ -208,9 +217,49 @@ beforeAll(async () => { }, })) + mock.module("@/context/server-sdk", () => ({ + useServerSDK: () => () => ({ + currentClient: { + commands: { + list: async () => ({ + location: { directory: "/repo/main", project: { id: "project", directory: "/repo/main" } }, + data: currentCommands, + }), + }, + sessions: { + create: async (input: { location?: { directory?: string } }) => { + await createSessionGate + const directory = input.location?.directory ?? "/repo/main" + createdSessions.push(directory) + sessionLocations[`session-${createdSessions.length}`] = directory + return { + id: `session-${createdSessions.length}`, + title: `New session ${createdSessions.length}`, + } + }, + prompt: async (input: unknown) => { + promptAsyncCalls.push(input) + return { id: "input-1" } + }, + interrupt: async () => undefined, + shell: async (input: { sessionID: string }) => { + sentShell.push(sessionLocations[input.sessionID] ?? input.sessionID) + }, + queueDrainResume: async (input: { sessionID: string }) => { + resumedQueues.push(input.sessionID) + }, + command: async (input: unknown) => { + commandCalls.push(input) + return { id: "input-command" } + }, + }, + }, + }), + })) + mock.module("@/context/sync", () => ({ useSync: () => () => ({ - data: { command: commands }, + data: { command: [] }, session: { optimistic: { add: (value: { @@ -233,13 +282,12 @@ beforeAll(async () => { })) mock.module("@/context/server-sync", () => ({ + loadMcpResourcesQuery, + loadMcpQuery, useServerSync: () => () => ({ session: { remember: () => undefined, set: () => undefined, - sync: async () => { - serverSessionSyncs++ - }, }, child: (directory: string) => { syncedDirectories.push(directory) @@ -281,27 +329,27 @@ beforeAll(async () => { beforeEach(() => { createdClients.length = 0 createdSessions.length = 0 - sessionCreateInputs.length = 0 enabledAutoAccept.length = 0 optimistic.length = 0 optimisticSeeded.length = 0 promoted.length = 0 promotedDrafts.length = 0 - sentPrompts.length = 0 - promptInputs.length = 0 - sentCommands.length = 0 - commands.length = 0 - promptValue = [{ type: "text", content: "ls", start: 0, end: 2 }] params = {} search = {} sentShell.length = 0 syncedDirectories.length = 0 + queuedDrafts.length = 0 + promptAsyncCalls.length = 0 + commandCalls.length = 0 + currentCommands.length = 0 + resumedQueues.length = 0 selected = "/repo/worktree-a" variant = undefined permissionServer = "server-a" createSessionGate = undefined - serverSessionSyncs = 0 + promptValue = [{ type: "text", content: "ls", start: 0, end: 2 }] for (const key of Object.keys(storedSessions)) delete storedSessions[key] + for (const key of Object.keys(sessionLocations)) delete sessionLocations[key] }) describe("prompt submit worktree selection", () => { @@ -334,29 +382,31 @@ describe("prompt submit worktree selection", () => { expect(createdClients).toEqual(["/repo/worktree-a", "/repo/worktree-b"]) expect(createdSessions).toEqual(["/repo/worktree-a", "/repo/worktree-b"]) - expect(sessionCreateInputs).toEqual([ + expect(sentShell).toEqual(["/repo/worktree-a", "/repo/worktree-b"]) + expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-b"]) + expect(promoted).toEqual([ { - agent: "agent", - model: { id: "model", providerID: "provider", variant: undefined }, - location: { directory: "/repo/worktree-a" }, + directory: "/repo/worktree-a", + sessionID: "session-1", + selection: { + agent: "agent", + model: { providerID: "provider", modelID: "model" }, + variant: null, + source: "user", + }, }, { - agent: "agent", - model: { id: "model", providerID: "provider", variant: undefined }, - location: { directory: "/repo/worktree-b" }, + directory: "/repo/worktree-b", + sessionID: "session-2", + selection: { + agent: "agent", + model: { providerID: "provider", modelID: "model" }, + variant: null, + source: "user", + }, }, ]) - expect(sentShell).toEqual([ - expect.objectContaining({ sessionID: "session-1", id: expect.stringMatching(/^evt_/), command: "ls" }), - expect.objectContaining({ sessionID: "session-2", id: expect.stringMatching(/^evt_/), command: "ls" }), - ]) - expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-a", "/repo/worktree-b", "/repo/worktree-b"]) - expect(serverSessionSyncs).toBe(0) - expect(promoted).toEqual([ - { directory: "/repo/worktree-a", sessionID: "session-1" }, - { directory: "/repo/worktree-b", sessionID: "session-2" }, - ]) - expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-a", "/repo/worktree-b", "/repo/worktree-b"]) + expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-b"]) }) test("applies auto-accept to newly created sessions", async () => { @@ -447,13 +497,11 @@ describe("prompt submit worktree selection", () => { expect(promotedDrafts).toEqual([{ draftID: "draft-1", server: "project-server", sessionId: "session-1" }]) }) - test("includes the selected variant on optimistic prompts", async () => { - params = { id: "session-1" } + test("promotes new sessions with the submitted model selection", async () => { variant = "high" - const submit = createPromptSubmit({ prompt, - info: () => ({ id: "session-1" }), + info: () => undefined, imageAttachments: () => [], commentCount: () => 0, autoAccept: () => false, @@ -466,39 +514,30 @@ describe("prompt submit worktree selection", () => { resetHistoryNavigation: () => undefined, setMode: () => undefined, setPopover: () => undefined, + newSessionWorktree: () => selected, + onNewSessionWorktreeReset: () => undefined, onSubmit: () => undefined, }) - const event = { preventDefault: () => undefined } as unknown as Event - - await submit.handleSubmit(event) - await Bun.sleep(0) + await submit.handleSubmit({ preventDefault: () => undefined } as unknown as Event) - expect(optimistic).toHaveLength(1) - expect(optimistic[0]).toMatchObject({ - message: { - agent: "agent", - model: { providerID: "provider", modelID: "model", variant: "high" }, + expect(promoted).toEqual([ + { + directory: "/repo/worktree-a", + sessionID: "session-1", + selection: { + agent: "agent", + model: { providerID: "provider", modelID: "model" }, + variant: "high", + source: "user", + }, }, - }) - expect(sentPrompts).toEqual(["/repo/main"]) - expect(promptInputs[0]).toMatchObject({ - sessionID: "session-1", - text: "ls", - files: [], - agents: [], - }) - expect((promptInputs[0] as { id?: string }).id).toStartWith("msg_") - expect((promptInputs[0] as { legacyParts?: { id: string; type: string; text?: string }[] }).legacyParts).toEqual([ - { id: expect.stringMatching(/^prt_/), type: "text", text: "ls" }, ]) }) - test("submits slash commands through the current session API", async () => { + test("includes the selected variant on durable current prompts", async () => { params = { id: "session-1" } variant = "high" - commands.push({ name: "review" }) - promptValue = [{ type: "text", content: "/review staged changes", start: 0, end: 22 }] const submit = createPromptSubmit({ prompt, @@ -515,22 +554,22 @@ describe("prompt submit worktree selection", () => { resetHistoryNavigation: () => undefined, setMode: () => undefined, setPopover: () => undefined, + onSubmit: () => undefined, }) - await submit.handleSubmit({ preventDefault: () => undefined } as unknown as Event) + const event = { preventDefault: () => undefined } as unknown as Event - expect(sentCommands).toEqual([ - { - sessionID: "session-1", - id: expect.stringMatching(/^msg_/), - command: "review", - arguments: "staged changes", + await submit.handleSubmit(event) + + expect(promptAsyncCalls).toHaveLength(1) + expect(resumedQueues).toEqual(["session-1"]) + expect(promptAsyncCalls[0]).toMatchObject({ + sessionID: "session-1", + payload: { agent: "agent", - model: { id: "model", providerID: "provider", variant: "high" }, - files: [], + model: { providerID: "provider", modelID: "model", variant: "high" }, }, - ]) - expect(serverSessionSyncs).toBe(0) + }) }) test("uses an injected model selection", async () => { @@ -559,14 +598,14 @@ describe("prompt submit worktree selection", () => { await submit.handleSubmit({ preventDefault: () => undefined } as unknown as Event) - expect(optimistic[0]).toMatchObject({ - message: { + expect(promptAsyncCalls[0]).toMatchObject({ + payload: { model: { providerID: "draft-provider", modelID: "draft-model", variant: "draft-variant" }, }, }) }) - test("seeds new sessions before optimistic prompts are added", async () => { + test("creates the current session before admitting its first prompt", async () => { const submit = createPromptSubmit({ prompt, info: () => undefined, @@ -591,8 +630,344 @@ describe("prompt submit worktree selection", () => { await submit.handleSubmit(event) - expect(storedSessions["/repo/worktree-a"]).toHaveLength(1) - expect(storedSessions["/repo/worktree-a"]?.[0]).toMatchObject({ id: "session-1", title: "New session 1" }) - expect(optimisticSeeded).toEqual([true]) + expect(createdSessions).toEqual(["/repo/worktree-a"]) + expect(promptAsyncCalls).toEqual([ + expect.objectContaining({ + sessionID: "session-1", + payload: expect.objectContaining({ agent: "agent" }), + }), + ]) + }) +}) + +describe("prompt submit queue mode", () => { + test("recognizes queued slash commands from the current catalog before routing the draft", async () => { + params = { id: "session-1" } + promptValue = [{ type: "text", content: "/review now", start: 0, end: 11 }] + currentCommands.push({ name: "review", template: "Review $ARGUMENTS" }) + + const submit = createPromptSubmit({ + prompt, + info: () => ({ id: "session-1" }), + imageAttachments: () => [], + commentCount: () => 0, + autoAccept: () => false, + mode: () => "normal", + working: () => true, + editor: () => undefined, + queueScroll: () => undefined, + promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0), + addToHistory: () => undefined, + resetHistoryNavigation: () => undefined, + setMode: () => undefined, + setPopover: () => undefined, + shouldQueue: () => true, + onQueue: (draft) => { + queuedDrafts.push(draft) + }, + }) + + await submit.handleSubmit({ preventDefault: () => undefined } as unknown as Event) + + expect(queuedDrafts).toEqual([ + expect.objectContaining({ + command: { name: "review", arguments: "now" }, + }), + ]) + expect(commandCalls).toHaveLength(0) + expect(promptAsyncCalls).toHaveLength(0) + }) + + test("gives direct custom command submissions a stable retry id", async () => { + params = { id: "session-1" } + promptValue = [{ type: "text", content: "/review now", start: 0, end: 11 }] + currentCommands.push({ name: "review", template: "Review $ARGUMENTS" }) + + const submit = createPromptSubmit({ + prompt, + info: () => ({ id: "session-1" }), + imageAttachments: () => [], + commentCount: () => 0, + autoAccept: () => false, + mode: () => "normal", + working: () => false, + editor: () => undefined, + queueScroll: () => undefined, + promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0), + addToHistory: () => undefined, + resetHistoryNavigation: () => undefined, + setMode: () => undefined, + setPopover: () => undefined, + }) + + await submit.handleSubmit({ preventDefault: () => undefined } as unknown as Event) + + expect(commandCalls).toEqual([ + expect.objectContaining({ + id: expect.stringMatching(/^msg_/), + sessionID: "session-1", + name: "review", + arguments: "now", + }), + ]) + expect(promptAsyncCalls).toHaveLength(0) + }) + + test("queueMode routes existing sessions through onQueue", async () => { + params = { id: "session-1" } + + const submit = createPromptSubmit({ + prompt, + info: () => ({ id: "session-1" }), + imageAttachments: () => [], + commentCount: () => 0, + autoAccept: () => false, + mode: () => "normal", + working: () => false, + editor: () => undefined, + queueScroll: () => undefined, + promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0), + addToHistory: () => undefined, + resetHistoryNavigation: () => undefined, + setMode: () => undefined, + setPopover: () => undefined, + queueMode: () => true, + resetQueueMode: () => undefined, + onQueue: (draft) => { + queuedDrafts.push(draft) + }, + onSubmit: () => undefined, + }) + + const event = { preventDefault: () => undefined } as unknown as Event + + await submit.handleSubmit(event) + + expect(queuedDrafts).toHaveLength(1) + expect(promptAsyncCalls).toHaveLength(0) + }) + + test("editingQueueID is forwarded on queue submit", async () => { + params = { id: "session-1" } + + const submit = createPromptSubmit({ + prompt, + info: () => ({ id: "session-1" }), + imageAttachments: () => [], + commentCount: () => 0, + autoAccept: () => false, + mode: () => "normal", + working: () => false, + editor: () => undefined, + queueScroll: () => undefined, + promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0), + addToHistory: () => undefined, + resetHistoryNavigation: () => undefined, + setMode: () => undefined, + setPopover: () => undefined, + queueMode: () => true, + resetQueueMode: () => undefined, + editingQueueID: () => "pqu_edit", + resetEditingQueueID: () => undefined, + onQueue: (draft) => { + queuedDrafts.push(draft) + }, + onSubmit: () => undefined, + }) + + const event = { preventDefault: () => undefined } as unknown as Event + + await submit.handleSubmit(event) + + expect(queuedDrafts).toHaveLength(1) + expect(queuedDrafts[0]).toMatchObject({ queueID: "pqu_edit" }) + }) + + test("editing a queued prompt retains the complete durable payload", async () => { + params = { id: "session-1" } + const payload = { + version: 1 as const, + agent: "reviewer", + model: { providerID: "provider", modelID: "model", variant: "high" }, + tools: { bash: false }, + system: "exact system", + format: { type: "text" as const }, + parts: [{ type: "text" as const, text: "hidden", synthetic: true }], + } + + const submit = createPromptSubmit({ + prompt, + info: () => ({ id: "session-1" }), + imageAttachments: () => [], + commentCount: () => 0, + autoAccept: () => false, + mode: () => "normal", + working: () => false, + editor: () => undefined, + queueScroll: () => undefined, + promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0), + addToHistory: () => undefined, + resetHistoryNavigation: () => undefined, + setMode: () => undefined, + setPopover: () => undefined, + editingQueueID: () => "pqu_edit", + editingQueuePayload: () => payload, + onQueue: (draft) => { + queuedDrafts.push(draft) + }, + }) + + await submit.handleSubmit({ preventDefault: () => undefined } as unknown as Event) + + expect(queuedDrafts[0]).toMatchObject({ + queueID: "pqu_edit", + queuePayload: payload, + }) + }) + + test("waits for durable queue persistence before completing an edit", async () => { + params = { id: "session-1" } + const order: string[] = [] + let release = () => {} + const gate = new Promise((resolve) => { + release = resolve + }) + + const submit = createPromptSubmit({ + prompt, + info: () => ({ id: "session-1" }), + imageAttachments: () => [], + commentCount: () => 0, + autoAccept: () => false, + mode: () => "normal", + working: () => false, + editor: () => undefined, + queueScroll: () => undefined, + promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0), + addToHistory: () => undefined, + resetHistoryNavigation: () => undefined, + setMode: () => undefined, + setPopover: () => undefined, + editingQueueID: () => "pqu_edit", + resetEditingQueueID: () => order.push("reset"), + onQueue: async () => { + await gate + order.push("persisted") + }, + }) + + const pending = submit.handleSubmit({ preventDefault: () => undefined } as unknown as Event) + await Promise.resolve() + expect(order).toEqual([]) + release() + await pending + expect(order).toEqual(["persisted", "reset"]) + }) + + test("editingQueueID commits through onQueue when queue mode is inactive", async () => { + params = { id: "session-1" } + + const submit = createPromptSubmit({ + prompt, + info: () => ({ id: "session-1" }), + imageAttachments: () => [], + commentCount: () => 0, + autoAccept: () => false, + mode: () => "normal", + working: () => false, + editor: () => undefined, + queueScroll: () => undefined, + promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0), + addToHistory: () => undefined, + resetHistoryNavigation: () => undefined, + setMode: () => undefined, + setPopover: () => undefined, + shouldQueue: () => false, + editingQueueID: () => "pqu_edit", + resetEditingQueueID: () => undefined, + onQueue: (draft) => { + queuedDrafts.push(draft) + }, + onSubmit: () => undefined, + }) + + const event = { preventDefault: () => undefined } as unknown as Event + + await submit.handleSubmit(event) + + expect(queuedDrafts).toHaveLength(1) + expect(queuedDrafts[0]).toMatchObject({ queueID: "pqu_edit" }) + expect(promptAsyncCalls).toHaveLength(0) + }) + + test("shouldQueue routes existing sessions through onQueue", async () => { + params = { id: "session-1" } + + const submit = createPromptSubmit({ + prompt, + info: () => ({ id: "session-1" }), + imageAttachments: () => [], + commentCount: () => 0, + autoAccept: () => false, + mode: () => "normal", + working: () => false, + editor: () => undefined, + queueScroll: () => undefined, + promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0), + addToHistory: () => undefined, + resetHistoryNavigation: () => undefined, + setMode: () => undefined, + setPopover: () => undefined, + shouldQueue: () => true, + onQueue: (draft) => { + queuedDrafts.push(draft) + }, + onSubmit: () => undefined, + }) + + const event = { preventDefault: () => undefined } as unknown as Event + + await submit.handleSubmit(event) + + expect(queuedDrafts).toHaveLength(1) + expect(promptAsyncCalls).toHaveLength(0) + }) +}) + +describe("sendFollowupDraft delivery", () => { + test("forwards delivery and the complete payload to the current prompt API", async () => { + const { sendFollowupDraft } = await import("./submit") + const calls: unknown[] = [] + + await sendFollowupDraft({ + client: { + sessions: { + prompt: async (input: unknown) => { + calls.push(input) + return { id: "input-1" } + }, + }, + } as never, + draft: { + sessionID: "session-1", + sessionDirectory: "/repo/main", + prompt: [{ type: "text", content: "follow up", start: 0, end: 9 }], + context: [], + agent: "agent", + model: { providerID: "provider", modelID: "model" }, + }, + delivery: "queue", + }) + + expect(calls).toEqual([ + expect.objectContaining({ + delivery: "queue", + sessionID: "session-1", + payload: expect.objectContaining({ + agent: "agent", + model: { providerID: "provider", modelID: "model" }, + }), + }), + ]) }) }) diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index ab0e621fc340..5b73dacd42bf 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -1,27 +1,24 @@ -import type { Message, Session } from "@opencode-ai/sdk/v2/client" -import { showToast } from "@/utils/toast" +import { showToast } from "@opencode-ai/ui/toast" import { base64Encode } from "@opencode-ai/core/util/encode" -import { Binary } from "@opencode-ai/core/util/binary" import { useNavigate, useParams, useSearchParams } from "@solidjs/router" import { batch, startTransition, type Accessor } from "solid-js" import { useTabs } from "@/context/tabs" -import { useServerSync, type ServerSync } from "@/context/server-sync" +import { useServerSync } from "@/context/server-sync" import { useLanguage } from "@/context/language" import { useLayout } from "@/context/layout" import { useLocal, type ModelSelection } from "@/context/local" import { usePermission } from "@/context/permission" import { type ContextItem, type ImageAttachmentPart, type Prompt, type usePrompt } from "@/context/prompt" -import { useSDK, type DirectorySDK } from "@/context/sdk" -import { useSync, type DirectorySync } from "@/context/sync" +import { useSDK } from "@/context/sdk" +import type { CurrentClient, SessionsPromptInput } from "@/utils/current-client" +import { useServerSDK } from "@/context/server-sdk" +import { useSync } from "@/context/sync" import { Identifier } from "@/utils/id" import { Worktree as WorktreeState } from "@/utils/worktree" -import { buildRequestParts } from "./build-request-parts" import { setCursorPosition } from "./editor-dom" -import { formatServerError } from "@/utils/server-errors" import { ScopedKey } from "@/utils/server-scope" import { createPromptSubmissionState } from "./submission-state" -import { normalizeSessionInfo } from "@/utils/session" -import { Event } from "@opencode-ai/schema/event" +import { createSessionPayload } from "./session-payload" type PendingPrompt = { abort: AbortController @@ -38,167 +35,33 @@ export type FollowupDraft = { agent: string model: { providerID: string; modelID: string } variant?: string + /** When set, save replaces this queued item instead of appending. */ queueID?: string - queuePayload?: import("@/utils/current-client").SessionsPromptInput["payload"] + /** Original durable payload retained while editing so hidden controls and parts survive. */ + queuePayload?: SessionsPromptInput["payload"] + /** A recognized custom slash command that still needs server-side expansion. */ command?: { name: string; arguments: string } } type FollowupSendInput = { - api: DirectorySDK["api"]["session"] - serverSync: ServerSync - sync: DirectorySync + client: CurrentClient draft: FollowupDraft messageID?: string - optimisticBusy?: boolean + delivery?: SessionsPromptInput["delivery"] before?: () => Promise | boolean } -const draftText = (prompt: Prompt) => prompt.map((part) => ("content" in part ? part.content : "")).join("") - -const draftImages = (prompt: Prompt) => prompt.filter((part): part is ImageAttachmentPart => part.type === "image") - export async function sendFollowupDraft(input: FollowupSendInput) { - const text = draftText(input.draft.prompt) - const images = draftImages(input.draft.prompt) - const setBusy = () => { - if (!input.optimisticBusy) return - input.serverSync.session.set("session_status", input.draft.sessionID, { type: "busy" }) - } - - const setIdle = () => { - if (!input.optimisticBusy) return - input.serverSync.session.set("session_status", input.draft.sessionID, { type: "idle" }) - } - - const wait = async () => { - const ok = await input.before?.() - if (ok === false) return false - return true - } - - const [head, ...tail] = text.split(" ") - const cmd = head?.startsWith("/") ? head.slice(1) : undefined - if (cmd && input.sync.data.command.find((item) => item.name === cmd)) { - setBusy() - try { - if (!(await wait())) { - setIdle() - return false - } - - const messageID = Identifier.ascending("message") - await input.api.command({ - sessionID: input.draft.sessionID, - id: messageID, - command: cmd, - arguments: tail.join(" "), - agent: input.draft.agent, - model: { - id: input.draft.model.modelID, - providerID: input.draft.model.providerID, - variant: input.draft.variant, - }, - files: images.map((attachment) => ({ - uri: attachment.dataUrl, - name: attachment.filename, - })), - }) - return true - } catch (err) { - setIdle() - throw err - } - } - const messageID = input.messageID ?? Identifier.ascending("message") - const { requestParts, optimisticParts } = buildRequestParts({ - prompt: input.draft.prompt, - context: input.draft.context, - images, - text, + if ((await input.before?.()) === false) return false + await input.client.sessions.prompt({ sessionID: input.draft.sessionID, - messageID, - sessionDirectory: input.draft.sessionDirectory, - }) - - const message: Message = { id: messageID, - sessionID: input.draft.sessionID, - role: "user", - time: { created: Date.now() }, - agent: input.draft.agent, - model: { ...input.draft.model, variant: input.draft.variant }, - } - - const add = () => - input.sync.session.optimistic.add({ - directory: input.draft.sessionDirectory, - sessionID: input.draft.sessionID, - message, - parts: optimisticParts, - }) - - const remove = () => - input.sync.session.optimistic.remove({ - directory: input.draft.sessionDirectory, - sessionID: input.draft.sessionID, - messageID, - }) - - batch(() => { - setBusy() - add() + payload: createSessionPayload(input.draft), + delivery: input.delivery, + resume: true, }) - - try { - if (!(await wait())) { - batch(() => { - setIdle() - remove() - }) - return false - } - - await input.api.prompt({ - sessionID: input.draft.sessionID, - id: messageID, - agent: input.draft.agent, - model: input.draft.model, - variant: input.draft.variant, - legacyParts: requestParts, - text: requestParts.flatMap((part) => (part.type === "text" ? [part.text] : [])).join("\n"), - files: requestParts.flatMap((part) => { - if (part.type !== "file") return [] - const text = part.source?.text - return [ - { - uri: part.url, - name: part.filename, - mention: text ? { start: text.start, end: text.end, text: text.value } : undefined, - }, - ] - }), - agents: requestParts.flatMap((part) => - part.type === "agent" - ? [ - { - name: part.name, - mention: part.source - ? { start: part.source.start, end: part.source.end, text: part.source.value } - : undefined, - }, - ] - : [], - ), - }) - return true - } catch (err) { - batch(() => { - setIdle() - remove() - }) - throw err - } + return true } type PromptSubmitInput = { @@ -219,7 +82,12 @@ type PromptSubmitInput = { newSessionWorktree?: Accessor onNewSessionWorktreeReset?: () => void shouldQueue?: Accessor - onQueue?: (draft: FollowupDraft) => void + queueMode?: Accessor + resetQueueMode?: () => void + editingQueueID?: Accessor + editingQueuePayload?: Accessor + resetEditingQueueID?: () => void + onQueue?: (draft: FollowupDraft) => Promise | void onAbort?: () => void onSubmit?: () => void model?: ModelSelection @@ -228,6 +96,7 @@ type PromptSubmitInput = { export function createPromptSubmit(input: PromptSubmitInput) { const navigate = useNavigate() const sdk = useSDK() + const serverSDK = useServerSDK() const sync = useSync() const serverSync = useServerSync() const local = useLocal() @@ -241,7 +110,6 @@ export function createPromptSubmit(input: PromptSubmitInput) { const pendingKey = (sessionID: string) => ScopedKey.from(sdk().scope, sessionID) const errorMessage = (err: unknown) => { - if (err && typeof err === "object" && "message" in err && typeof err.message === "string") return err.message if (err && typeof err === "object" && "data" in err) { const data = (err as { data?: { message?: string } }).data if (data?.message) return data.message @@ -266,9 +134,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { pending.delete(key) return Promise.resolve() } - return sdk() - .api.session.interrupt({ sessionID }) - .catch(() => {}) + return serverSDK().currentClient.sessions.interrupt({ sessionID }).catch(() => {}) } const restoreCommentItems = ( @@ -294,21 +160,6 @@ export function createPromptSubmit(input: PromptSubmitInput) { } } - const seed = (dir: string, info: Session) => { - serverSync().session.remember(info) - const [, setStore] = serverSync().child(dir) - setStore("session", (list: Session[]) => { - const result = Binary.search(list, info.id, (item) => item.id) - const next = [...list] - if (result.found) { - next[result.index] = info - return next - } - next.splice(result.index, 0, info) - return next - }) - } - const handleSubmit = async (event: Event) => { event.preventDefault() @@ -323,16 +174,17 @@ export function createPromptSubmit(input: PromptSubmitInput) { const text = currentPrompt.map((part) => ("content" in part ? part.content : "")).join("") const images = input.imageAttachments().slice() const mode = input.mode() + const queueMode = input.queueMode?.() ?? false if (text.trim().length === 0 && images.length === 0 && input.commentCount() === 0) { if (input.working()) void abort() return } - const modelSelection = input.model ?? local.model - const currentModel = modelSelection.current() + const modelState = input.model ?? local.model + const currentModel = modelState.current() const currentAgent = local.agent.current() - const variant = modelSelection.variant.current() + const variant = modelState.variant.current() if (!currentModel || !currentAgent) { showToast({ title: language.t("prompt.toast.modelAgentRequired.title"), @@ -349,6 +201,16 @@ export function createPromptSubmit(input: PromptSubmitInput) { const isNewSession = !params.id const shouldAutoAccept = isNewSession && input.autoAccept() const worktreeSelection = input.newSessionWorktree?.() || "main" + const model = { + modelID: currentModel.id, + providerID: currentModel.provider.id, + } + const selectedModel = { + ...model, + ...(currentModel.name ? { name: currentModel.name } : {}), + ...(currentModel.provider.name ? { providerName: currentModel.provider.name } : {}), + } + const agent = currentAgent.name let sessionDirectory = projectDirectory let client = sdk().client @@ -392,15 +254,14 @@ export function createPromptSubmit(input: PromptSubmitInput) { input.onNewSessionWorktreeReset?.() } - let session = input.info() + let session = input.info() ?? (params.id ? { id: params.id } : undefined) if (!session && isNewSession) { - const created = await sdk() - .api.session.create({ - agent: currentAgent.name, - model: { id: currentModel.id, providerID: currentModel.provider.id, variant }, + const created = await serverSDK().currentClient.sessions + .create({ + agent, + model: { id: model.modelID, providerID: model.providerID, variant }, location: { directory: sessionDirectory }, }) - .then(normalizeSessionInfo) .catch((err) => { showToast({ title: language.t("prompt.toast.sessionCreateFailed.title"), @@ -409,15 +270,15 @@ export function createPromptSubmit(input: PromptSubmitInput) { return undefined }) if (created) { - seed(sessionDirectory, created) session = created await startTransition(() => { if (!session) return if (shouldAutoAccept) permissionState.enableAutoAccept(session.id, sessionDirectory) local.session.promote(sessionDirectory, session.id, { - agent: currentAgent.name, - model: { providerID: currentModel.provider.id, modelID: currentModel.id }, + agent, + model: selectedModel, variant: variant ?? null, + source: "user", }) layout.handoff.setTabs(base64Encode(sessionDirectory), session.id) const draftID = search.draftId @@ -435,11 +296,22 @@ export function createPromptSubmit(input: PromptSubmitInput) { return } - const model = { - modelID: currentModel.id, - providerID: currentModel.provider.id, - } - const agent = currentAgent.name + const commandInput = mode === "normal" ? text.match(/^\/(\S+)(?:[ \t]+([\s\S]*))?$/) : undefined + const commandName = commandInput?.[1] + const commands = commandName + ? await serverSDK().currentClient.commands + .list({ location: { directory: sessionDirectory } }) + .then((result) => result.data) + .catch((err) => { + showToast({ + title: language.t("prompt.toast.commandSendFailed.title"), + description: errorMessage(err), + }) + return undefined + }) + : [] + if (commandName && !commands) return + const customCommand = commands?.find((command) => command.name === commandName) const draft: FollowupDraft = { sessionID: session.id, sessionDirectory, @@ -448,6 +320,11 @@ export function createPromptSubmit(input: PromptSubmitInput) { agent, model, variant, + queueID: input.editingQueueID?.(), + queuePayload: input.editingQueuePayload?.(), + ...(customCommand && commandName + ? { command: { name: commandName, arguments: commandInput?.[2] ?? "" } } + : {}), } const clearInput = () => { @@ -473,26 +350,34 @@ export function createPromptSubmit(input: PromptSubmitInput) { return true } - if (!isNewSession && mode === "normal" && input.shouldQueue?.()) { - input.onQueue?.(draft) + if (!isNewSession && mode === "normal" && (draft.queueID || input.shouldQueue?.() || queueMode)) { + const saved = await Promise.resolve(input.onQueue?.(draft)) + .then(() => true) + .catch((err) => { + showToast({ + title: language.t("common.requestFailed"), + description: errorMessage(err), + }) + return false + }) + if (!saved) return + input.resetQueueMode?.() + if (draft.queueID) input.resetEditingQueueID?.() clearContext(submission.target()) clearInput() return } + input.resetQueueMode?.() + + void serverSDK().currentClient.sessions.queueDrainResume({ sessionID: session.id }).catch(() => {}) + input.onSubmit?.() if (mode === "shell") { clearInput() - const eventID = Event.ID.create() - sdk() - .api.session.shell({ - sessionID: session.id, - id: eventID, - command: text, - agent, - model, - }) + serverSDK().currentClient.sessions + .shell({ sessionID: session.id, command: text }) .catch((err) => { showToast({ title: language.t("prompt.toast.shellSendFailed.title"), @@ -503,37 +388,25 @@ export function createPromptSubmit(input: PromptSubmitInput) { return } - if (text.startsWith("/")) { - const [cmdName, ...args] = text.split(" ") - const commandName = cmdName.slice(1) - const customCommand = sync().data.command.find((c) => c.name === commandName) - if (customCommand) { - clearInput() - const messageID = Identifier.ascending("message") - serverSync().session.set("session_status", session.id, { type: "busy" }) - sdk() - .api.session.command({ - sessionID: session.id, - id: messageID, - command: commandName, - arguments: args.join(" "), - agent, - model: { id: model.modelID, providerID: model.providerID, variant }, - files: images.map((attachment) => ({ - uri: attachment.dataUrl, - name: attachment.filename, - })), - }) - .catch((err) => { - serverSync().session.set("session_status", session.id, { type: "idle" }) - showToast({ - title: language.t("prompt.toast.commandSendFailed.title"), - description: formatServerError(err, language.t, language.t("common.requestFailed")), - }) - restoreInput() + if (draft.command) { + clearInput() + serverSDK().currentClient.sessions + .command({ + id: Identifier.ascending("message"), + sessionID: session.id, + name: draft.command.name, + arguments: draft.command.arguments, + payload: createSessionPayload(draft), + resume: true, + }) + .catch((err) => { + showToast({ + title: language.t("prompt.toast.commandSendFailed.title"), + description: errorMessage(err), }) - return - } + restoreInput() + }) + return } const commentItems = context.filter((item) => item.type === "file" && !!item.comment?.trim()) @@ -609,12 +482,9 @@ export function createPromptSubmit(input: PromptSubmitInput) { } void sendFollowupDraft({ - api: sdk().api.session, - sync: sync(), - serverSync: serverSync(), + client: serverSDK().currentClient, draft, messageID, - optimisticBusy: sessionDirectory === projectDirectory, before: waitForWorktree, }).catch((err) => { pending.delete(pendingKey(session.id)) diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx index 43d9c889c337..b459f0ccc2d9 100644 --- a/packages/app/src/components/session/session-context-tab.tsx +++ b/packages/app/src/components/session/session-context-tab.tsx @@ -92,7 +92,10 @@ function RawMessage(props: { const emptyMessages: readonly SessionMessage.Message[] = [] -export function SessionContextTab(props: { messages?: readonly SessionMessage.Message[]; session?: Session.Info }) { +export function SessionContextTab(props: { + messages?: readonly SessionMessage.Message[] + session?: Session.Info +}) { const language = useLanguage() const sdk = useSDK() const providers = useProviders(() => sdk().directory) diff --git a/packages/app/src/context/global-sync/bootstrap.test.ts b/packages/app/src/context/global-sync/bootstrap.test.ts index ad20f3c55457..a67b88a74bd3 100644 --- a/packages/app/src/context/global-sync/bootstrap.test.ts +++ b/packages/app/src/context/global-sync/bootstrap.test.ts @@ -132,7 +132,15 @@ describe("bootstrapDirectory", () => { expect(store.status).toBe("partial") - await new Promise((resolve) => setTimeout(resolve, 80)) + await new Promise((resolve, reject) => { + const deadline = Date.now() + 1_000 + const check = () => { + if (store.status === "complete" && mcpReads.length === 3) return resolve() + if (Date.now() >= deadline) return reject(new Error("bootstrap did not complete")) + setTimeout(check, 10) + } + check() + }) expect(store.status).toBe("complete") expect(mcpReads.sort()).toEqual(["command", "resource", "status"]) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 10f36cdc1d1c..20d9d118e4ff 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1,4 +1,4 @@ -import type { FilePart, Project, UserMessage, VcsFileDiff } from "@opencode-ai/sdk/v2" +import type { FilePart, Project, Session, UserMessage, VcsFileDiff } from "@opencode-ai/sdk/v2" import { getFilename } from "@opencode-ai/core/util/path" import { useDialog } from "@opencode-ai/ui/context/dialog" import { createQuery, skipToken, useMutation, useQueryClient } from "@tanstack/solid-query" @@ -63,7 +63,8 @@ import { PromptInputV2Composer, usePromptInputV2Controller } from "@/components/ import { useSettingsCommand } from "@/components/settings-dialog" import { setCursorPosition } from "@/components/prompt-input/editor-dom" import { promptLength } from "@/components/prompt-input/history" -import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit" +import { type FollowupDraft } from "@/components/prompt-input/submit" +import { promptFromSessionPayload } from "@/components/prompt-input/prompt-from-session-payload" import { createPromptInputController, createSessionComposerController, @@ -94,19 +95,15 @@ import { TerminalPanelV2 } from "@/pages/session/terminal-panel-v2" import { useComposerCommands } from "@/pages/session/use-composer-commands" import { useSessionCommands } from "@/pages/session/use-session-commands" import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll" -import { Identifier } from "@/utils/id" +import { queuedFollowup, saveQueuedFollowup } from "@/pages/session/session-queue" import { diffs as list } from "@/utils/diffs" -import { Persist, persisted } from "@/utils/persist" import { extractPromptFromParts } from "@/utils/prompt" import { formatServerError, isLocalSessionNotFoundError, isSessionNotFoundError } from "@/utils/server-errors" import { legacySessionHref, requireServerKey, sessionHref } from "@/utils/session-route" import { useUsageExceededDialogs } from "./session/usage-exceeded-dialogs" import { createSessionOwnership } from "./session/session-ownership" import { createSessionLineage } from "./session/session-lineage" - -type FollowupItem = FollowupDraft & { id: string } -type FollowupEdit = Pick -const emptyFollowups: FollowupItem[] = [] +import { useCurrentSession } from "./session/current" type ChangeMode = "git" | "branch" | "turn" type VcsMode = "git" | "branch" @@ -398,13 +395,6 @@ export default function Page() { }, }) - const composer = createSessionComposerController() - const inputController = createPromptInputController({ - sessionKey, - sessionID: () => params.id, - queryOptions: serverSync().queryOptions, - }) - const workspaceTabs = createMemo(() => layout.tabs(workspaceKey)) const sessionPanelKey = createMemo(() => (params.id ? `${serverSDK().scope}\0${params.id}` : undefined)) @@ -531,7 +521,31 @@ export default function Page() { if (!view().reviewPanel.opened()) view().reviewPanel.open() } - const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined)) + const current = useCurrentSession(() => params.id) + const busy = (sessionID: string) => + sessionID === params.id ? current.busy() || sync().data.session_working(sessionID) : sync().data.session_working(sessionID) + createEffect( + on( + () => [params.id, current.readiness(), current.busy()] as const, + ([id, readiness, active]) => { + if (!id || readiness !== "ready") return + const next = active ? ({ type: "busy" } as const) : ({ type: "idle" } as const) + if ((sync().data.session_status[id]?.type ?? "idle") === next.type) return + sync().set("session_status", id, next) + }, + ), + ) + const composer = createSessionComposerController({ + working: () => !!params.id && busy(params.id), + ready: () => current.readiness() === "ready", + }) + const inputController = createPromptInputController({ + sessionKey, + sessionID: () => params.id, + queryOptions: serverSync().queryOptions, + working: () => !!params.id && busy(params.id), + }) + const info = createMemo(() => current.session() ?? (params.id ? sync().session.get(params.id) : undefined)) const isChildSession = createMemo(() => !!info()?.parentID) const canReview = createMemo(() => !!sync().project) const reviewTab = createMemo(() => isDesktop()) @@ -545,6 +559,7 @@ export default function Page() { const activeTab = tabState.activeTab const activeFileTab = tabState.activeFileTab const revertMessageID = createMemo(() => info()?.revert?.messageID) + const contextMessages = current.messages const timeline = createTimelineModel({ sessionID: () => params.id, revertMessageID }) const historyLoading = timeline.history.loading const historyMore = timeline.history.more @@ -574,6 +589,22 @@ export default function Page() { ), ) + createEffect( + on( + () => current.messages().findLast((message) => message.type === "user")?.id, + () => { + const message = current.messages().findLast((message) => message.type === "user") + const sessionID = params.id + if (!sessionID || message?.type !== "user" || !message.payload) return + syncSessionModel(local, { + sessionID, + agent: message.payload.agent, + model: message.payload.model, + }) + }, + ), + ) + let restoredModelSession: string | undefined createEffect(() => { const id = params.id @@ -603,21 +634,6 @@ export default function Page() { deferRender: false, }) - const [followup, setFollowup] = persisted( - Persist.serverWorkspace(serverSDK().scope, sdk().directory, "followup", ["followup.v1"]), - createStore<{ - items: Record - failed: Record - paused: Record - edit: Record - }>({ - items: {}, - failed: {}, - paused: {}, - edit: {}, - }), - ) - createComputed((prev) => { const key = sessionKey() if (key !== prev) { @@ -1692,7 +1708,7 @@ export default function Page() { }) } - const merge = (next: NonNullable>, target = sync()) => target.session.remember(next) + const merge = (next: Session, target = sync()) => target.session.remember(next) const roll = (sessionID: string, next: NonNullable>["revert"], target = sync()) => { const session = target.session.get(sessionID) @@ -1700,49 +1716,47 @@ export default function Page() { target.session.remember({ ...session, revert: next }) } - const busy = (sessionID: string) => sync().data.session_working(sessionID) - - const queuedFollowups = createMemo(() => { - const id = params.id - if (!id) return emptyFollowups - return followup.items[id] ?? emptyFollowups - }) - - const editingFollowup = createMemo(() => { - const id = params.id - if (!id) return - return followup.edit[id] - }) + const queuedFollowups = createMemo(() => + current.queue().map((item) => ({ + id: item.id, + draft: queuedFollowup(item, sdk().directory, language.t("common.attachment")), + })), + ) + const [editingFollowup, setEditingFollowup] = createSignal() + createEffect(on(() => params.id, () => setEditingFollowup(undefined), { defer: true })) const followupMutation = useMutation(() => ({ mutationFn: async (input: { sessionID: string; id: string; manual?: boolean }) => { const owner = sessionOwnership.capture() - const item = (followup.items[input.sessionID] ?? []).find((entry) => entry.id === input.id) + const item = current.queue().find((entry) => entry.id === input.id) if (!item) return - - if (input.manual) setFollowup("paused", input.sessionID, undefined) - setFollowup("failed", input.sessionID, undefined) - - const ok = await sendFollowupDraft({ - api: sdk().api.session, - sync: sync(), - serverSync: serverSync(), - draft: item, - optimisticBusy: item.sessionDirectory === sdk().directory, - }).catch((err) => { - setFollowup("failed", input.sessionID, input.id) - fail(err) - return false - }) - if (!ok) return - - setFollowup("items", input.sessionID, (items) => (items ?? []).filter((entry) => entry.id !== input.id)) + await serverSDK() + .currentClient.sessions.queueSend({ sessionID: input.sessionID, messageID: input.id }) + .then(() => current.refreshQueue()) + .catch((err) => { + fail(err) + throw err + }) if (input.manual) owner.run(resumeScroll) }, })) + const removeFollowupMutation = useMutation(() => ({ + mutationFn: async (input: { sessionID: string; id: string }) => { + await serverSDK() + .currentClient.sessions.queueRemove({ sessionID: input.sessionID, messageID: input.id }) + .then(() => current.refreshQueue()) + .catch((err) => { + fail(err) + throw err + }) + if (editingFollowup()?.queueID === input.id) setEditingFollowup(undefined) + }, + })) + const followupBusy = (sessionID: string) => - followupMutation.isPending && followupMutation.variables?.sessionID === sessionID + (followupMutation.isPending && followupMutation.variables?.sessionID === sessionID) || + (removeFollowupMutation.isPending && removeFollowupMutation.variables?.sessionID === sessionID) const sendingFollowup = createMemo(() => { const id = params.id @@ -1774,21 +1788,21 @@ export default function Page() { return `[${language.t("common.attachment")}]` } - const queueFollowup = (draft: FollowupDraft) => { - setFollowup("items", draft.sessionID, (items) => [ - ...(items ?? []), - { id: Identifier.ascending("message"), ...draft }, - ]) - setFollowup("failed", draft.sessionID, undefined) - setFollowup("paused", draft.sessionID, undefined) + const queueFollowup = async (draft: FollowupDraft) => { + await saveQueuedFollowup({ client: serverSDK().currentClient.sessions, draft }) + .then(() => current.refreshQueue()) + .catch((err) => { + fail(err) + throw err + }) + if (draft.queueID) setEditingFollowup(undefined) } - const followupDock = createMemo(() => queuedFollowups().map((item) => ({ id: item.id, text: followupText(item) }))) + const followupDock = createMemo(() => queuedFollowups().map((item) => ({ id: item.id, text: followupText(item.draft) }))) const sendFollowup = (sessionID: string, id: string, opts?: { manual?: boolean }) => { - if (sync().session.get(sessionID)?.parentID) return Promise.resolve() - const item = (followup.items[sessionID] ?? []).find((entry) => entry.id === id) - if (!item) return Promise.resolve() + if (current.session()?.parentID) return Promise.resolve() + if (!current.queue().some((entry) => entry.id === id)) return Promise.resolve() if (followupBusy(sessionID)) return Promise.resolve() return followupMutation.mutateAsync({ sessionID, id, manual: opts?.manual }) @@ -1802,26 +1816,17 @@ export default function Page() { const item = queuedFollowups().find((entry) => entry.id === id) if (!item) return - setFollowup("items", sessionID, (items) => (items ?? []).filter((entry) => entry.id !== id)) - setFollowup("failed", sessionID, (value) => (value === id ? undefined : value)) - setFollowup("edit", sessionID, { - id: item.id, - prompt: item.prompt, - context: item.context, - }) + setEditingFollowup(item.draft) } const removeFollowup = (id: string) => { const sessionID = params.id if (!sessionID || followupBusy(sessionID)) return - setFollowup("items", sessionID, (items) => (items ?? []).filter((entry) => entry.id !== id)) - setFollowup("failed", sessionID, (value) => (value === id ? undefined : value)) + void removeFollowupMutation.mutateAsync({ sessionID, id }) } const clearFollowupEdit = () => { - const id = params.id - if (!id) return - setFollowup("edit", id, undefined) + setEditingFollowup(undefined) } const halt = (sessionID: string) => @@ -1928,22 +1933,6 @@ export default function Page() { const actions = { revert, openAttachment } - createEffect(() => { - const sessionID = params.id - if (!sessionID) return - - const item = queuedFollowups()[0] - if (!item) return - if (followupBusy(sessionID)) return - if (followup.failed[sessionID] === item.id) return - if (followup.paused[sessionID]) return - if (isChildSession()) return - if (composer.blocked()) return - if (busy(sessionID)) return - - void sendFollowup(sessionID, item.id) - }) - createResizeObserver( () => promptDock, ({ height }) => { @@ -2149,6 +2138,7 @@ export default function Page() { ? { items: followupDock(), sending: sendingFollowup(), + editingMessageID: editingFollowup()?.queueID, onSend: (id) => void sendFollowup(params.id!, id, { manual: true }), onEdit: editFollowup, onRemove: removeFollowup, @@ -2198,14 +2188,24 @@ export default function Page() { comments.clear() resumeScroll() }} - edit={editingFollowup()} - onEditLoaded={clearFollowupEdit} + edit={ + editingFollowup() + ? { + id: editingFollowup()!.queueID!, + prompt: editingFollowup()!.prompt, + context: editingFollowup()!.context, + } + : undefined + } + editingQueueID={() => editingFollowup()?.queueID} + editingQueuePayload={() => editingFollowup()?.queuePayload} + resetEditingQueueID={clearFollowupEdit} shouldQueue={queueEnabled} onQueue={queueFollowup} onAbort={() => { const id = params.id if (!id) return - setFollowup("paused", id, true) + void serverSDK().currentClient.sessions.queueDrainPause({ sessionID: id }) }} /> } @@ -2227,15 +2227,19 @@ export default function Page() { resumeScroll() }, get edit() { - return editingFollowup() + const edit = editingFollowup() + if (!edit) return + return { id: edit.queueID!, prompt: edit.prompt, context: edit.context } }, - onEditLoaded: clearFollowupEdit, + editingQueueID: () => editingFollowup()?.queueID, + editingQueuePayload: () => editingFollowup()?.queuePayload, + resetEditingQueueID: clearFollowupEdit, shouldQueue: queueEnabled, onQueue: queueFollowup, onAbort: () => { const id = params.id if (!id) return - setFollowup("paused", id, true) + void serverSDK().currentClient.sessions.queueDrainPause({ sessionID: id }) }, }) return @@ -2320,6 +2324,8 @@ export default function Page() { focusReviewDiff={focusReviewDiff} reviewSnap={ui.reviewSnap} size={size} + contextMessages={contextMessages} + contextSession={current.session} /> @@ -2351,6 +2357,8 @@ export default function Page() { reviewSnap={ui.reviewSnap} size={size} stacked={desktopV2PanelLayout().stacked} + contextMessages={contextMessages} + contextSession={current.session} /> diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx index 22c52e73feb8..e32cb5bd21cc 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -25,6 +25,8 @@ import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { FileDiffInfo } from "@opencode-ai/client/promise" +import type { SessionMessage } from "@opencode-ai/schema/session-message" +import type { Session } from "@opencode-ai/schema/session" import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd" import { useDialog } from "@opencode-ai/ui/context/dialog" @@ -80,6 +82,8 @@ export function SessionSidePanel(props: { reviewSnap: boolean size: Sizing stacked?: boolean + contextMessages: () => readonly SessionMessage.Message[] + contextSession: () => Session.Info | undefined }) { const layout = useLayout() const settings = useSettings() @@ -384,7 +388,11 @@ export function SessionSidePanel(props: { onMiddleClick={() => tabs().close("context")} >
- +
{language.t("session.tab.context")}
@@ -491,7 +499,7 @@ export function SessionSidePanel(props: {
- +
@@ -598,7 +606,11 @@ export function SessionSidePanel(props: { onMiddleClick={() => tabs().close("context")} >
- +
{language.t("session.tab.context")}
@@ -719,7 +731,7 @@ export function SessionSidePanel(props: {
- +
diff --git a/packages/app/src/pages/session/v2/session-file-browser-tab.tsx b/packages/app/src/pages/session/v2/session-file-browser-tab.tsx index dac069ca2800..c4f332838574 100644 --- a/packages/app/src/pages/session/v2/session-file-browser-tab.tsx +++ b/packages/app/src/pages/session/v2/session-file-browser-tab.tsx @@ -1,4 +1,4 @@ -import { createMemo, createSignal, createUniqueId, Show } from "solid-js" +import { createEffect, createMemo, createSignal, createUniqueId, on, onCleanup, Show } from "solid-js" import { createQuery } from "@tanstack/solid-query" import { Icon } from "@opencode-ai/ui/icon" import { SessionFilePanelV2, SessionFilePanelV2Empty } from "@opencode-ai/session-ui/v2/session-file-panel-v2" @@ -47,6 +47,8 @@ export function SessionFileBrowserTab(props: { const resultsID = `session-file-browser-results-${createUniqueId()}` const [filter, setFilter] = createSignal("") const [explicitHighlight, setExplicitHighlight] = createSignal() + let sidebarViewport: HTMLDivElement | undefined + let sidebarScrollTop = 0 const sidebarOpened = () => props.placeholder || props.state.sidebarOpened() const query = createMemo(() => filter().trim()) const search = createQuery(() => { @@ -81,6 +83,26 @@ export function SessionFileBrowserTab(props: { const title = createMemo(() => displayName(project() ?? { worktree: sdk().directory })) const optionID = (path: string) => `${resultsID}-option-${files().indexOf(path)}` + const setSidebarViewport = (element: HTMLDivElement) => { + sidebarViewport = element + const update = () => (sidebarScrollTop = element.scrollTop) + element.addEventListener("scroll", update) + onCleanup(() => element.removeEventListener("scroll", update)) + } + + createEffect( + on( + () => props.tab, + (_, previous) => { + if (previous === undefined) return + const scrollTop = sidebarScrollTop + requestAnimationFrame(() => { + if (sidebarViewport) sidebarViewport.scrollTop = scrollTop + }) + }, + ), + ) + const onFilterKeyDown = (event: KeyboardEvent & { currentTarget: HTMLInputElement }) => { if (event.key === "Escape" && query()) { event.preventDefault() @@ -114,6 +136,7 @@ export function SessionFileBrowserTab(props: { filterExpanded={query().length > 0 && files().length > 0} width={props.state.sidebarWidth()} onWidthChange={props.state.resizeSidebar} + viewportRef={setSidebarViewport} > + + {(queue) => ( + + {queue().label()} + + + } + > + + {queue().label()} + + + )} + onSubmit: () => void onStop: () => void + label?: Accessor + queue?: { + visible: Accessor + active: Accessor + label: Accessor + keybind: string[] + onToggle: () => void + onSubmit: () => void + } } shell?: { onOpen: () => void From 4b62f18f6c8edb21fe36bf92df07882e33ea4f06 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 1 Aug 2026 17:26:42 -0300 Subject: [PATCH 002/129] fix(opencode): restore queue and session model flow --- packages/core/src/catalog.ts | 1 + packages/core/test/catalog.test.ts | 18 +++ packages/core/test/config/config.test.ts | 31 +++++ .../opencode/scripts/adhoc-queue-qa-smoke.py | 108 ++++++++++-------- packages/opencode/src/cli/cmd/run.ts | 23 +++- .../src/cli/cmd/run/footer.prompt.tsx | 7 ++ .../opencode/src/cli/cmd/run/footer.view.tsx | 14 +++ packages/opencode/src/cli/cmd/run/runtime.ts | 5 +- .../src/cli/cmd/run/stream.current.ts | 4 +- packages/opencode/src/share/current.ts | 16 ++- .../test/cli/run/footer.view.test.tsx | 22 ++++ .../test/cli/run/interactive-mode.test.ts | 10 ++ .../test/cli/run/stream.current.test.ts | 9 ++ packages/opencode/test/share/current.test.ts | 4 + 14 files changed, 210 insertions(+), 62 deletions(-) create mode 100644 packages/opencode/test/cli/run/interactive-mode.test.ts diff --git a/packages/core/src/catalog.ts b/packages/core/src/catalog.ts index 1945024b54e8..cf8579777f31 100644 --- a/packages/core/src/catalog.ts +++ b/packages/core/src/catalog.ts @@ -71,6 +71,7 @@ const layer = Layer.effect( const available = (provider: ProviderV2.Info, integration: Integration.Info | undefined) => { if (provider.disabled) return false if (typeof provider.request.body.apiKey === "string") return true + if (provider.api.type === "aisdk" && typeof provider.api.settings?.apiKey === "string") return true if (integration?.connections.length) return true return provider.integrationID === undefined && !integration } diff --git a/packages/core/test/catalog.test.ts b/packages/core/test/catalog.test.ts index 6c736cde1e15..e84efbbe0026 100644 --- a/packages/core/test/catalog.test.ts +++ b/packages/core/test/catalog.test.ts @@ -101,6 +101,24 @@ describe("CatalogV2", () => { }).pipe(Effect.provide(localCatalogLayer)) }) + it.effect("derives availability from an AISDK provider API key", () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const providerID = ProviderV2.ID.make("test") + yield* catalog.transform((editor) => + editor.provider.update(providerID, (provider) => { + provider.api = { + type: "aisdk", + package: "@ai-sdk/openai-compatible", + settings: { apiKey: "test-key" }, + } + }), + ) + + expect((yield* catalog.provider.available()).map((provider) => provider.id)).toContain(providerID) + }), + ) + it.effect("projects environment connections without a catalog plugin", () => Effect.acquireUseRelease( Effect.sync(() => { diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index c3c42cab3022..a01c0653e7cf 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -115,6 +115,37 @@ describe("Config", () => { }), ) + it.effect("migrates a legacy custom provider into a usable v2 model", () => + Effect.sync(() => { + const input = { + formatter: false, + lsp: false, + provider: { + test: { + name: "Test", + env: [], + npm: "@ai-sdk/openai-compatible", + models: { + "test-model": { + id: "test-model", + name: "Test Model", + tool_call: true, + limit: { context: 100_000, output: 10_000 }, + cost: { input: 0, output: 0 }, + }, + }, + options: { apiKey: "test-key", baseURL: "http://127.0.0.1:1/v1" }, + }, + }, + } + const legacy = Schema.decodeUnknownSync(ConfigV1.Info)(input) + const migrated = Schema.decodeUnknownSync(Config.Info)(ConfigMigrateV1.migrate(legacy)) + + expect(migrated.providers?.test?.models?.["test-model"]?.api).toMatchObject({ id: "test-model" }) + expect(migrated.providers?.test?.api?.settings).toEqual({ apiKey: "test-key" }) + }), + ) + it.effect("migrates v1 command configuration", () => Effect.sync(() => { expect( diff --git a/packages/opencode/scripts/adhoc-queue-qa-smoke.py b/packages/opencode/scripts/adhoc-queue-qa-smoke.py index 1c84428a3b27..1814bcc78395 100755 --- a/packages/opencode/scripts/adhoc-queue-qa-smoke.py +++ b/packages/opencode/scripts/adhoc-queue-qa-smoke.py @@ -128,35 +128,44 @@ def has_queue_routes(binary: str) -> bool: LIVE = os.environ.get("OPENCODE_QA_LIVE", "") == "1" -def test_provider_config(llm_url: str) -> str: - return json.dumps( - { - "formatter": False, - "lsp": False, - "providers": { - "test": { - "name": "Test", - "env": [], - "api": { - "type": "aisdk", - "package": "@ai-sdk/openai-compatible", - "url": llm_url, - }, - "request": {"body": {"apiKey": "test-key"}}, - "models": { - "test-model": { - "api": {"id": "test-model"}, - "capabilities": { - "tools": True, - "input": ["text"], - "output": ["text"], - }, - "limit": {"context": 100_000, "output": 10_000}, - } - }, - } - }, - } +def write_test_provider_config(ws: Path, llm_url: str) -> None: + # Local CLI startup still validates the legacy shape; the V2 session runner + # migrates that same project config while building its catalog. + # The V2 location resolver stops its upward project-config search at the + # workspace root. Keep the compatibility fixture at that root so both the + # V1 CLI and V2 runner load the same provider. + config = ws / "opencode.json" + config.write_text( + json.dumps( + { + "formatter": False, + "lsp": False, + "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": 100_000, "output": 10_000}, + "cost": {"input": 0, "output": 0}, + "options": {}, + } + }, + "options": {"apiKey": "test-key", "baseURL": llm_url}, + } + }, + } + ), + encoding="utf-8", ) @@ -169,7 +178,6 @@ def isolated_env(home: Path, llm_url: str | None = None) -> dict[str, str]: "XDG_DATA_HOME": str(home / ".local/share"), "XDG_STATE_HOME": str(home / ".local/state"), "XDG_CACHE_HOME": str(home / ".cache"), - "OPENCODE_DISABLE_PROJECT_CONFIG": "1", "OPENCODE_PURE": "1", "OPENCODE_DISABLE_AUTOUPDATE": "1", "OPENCODE_DISABLE_AUTOCOMPACT": "1", @@ -178,8 +186,6 @@ def isolated_env(home: Path, llm_url: str | None = None) -> dict[str, str]: "TERM": os.environ.get("TERM", "xterm-256color"), "COLORTERM": os.environ.get("COLORTERM", "truecolor"), } - if llm_url: - env["OPENCODE_CONFIG_CONTENT"] = test_provider_config(llm_url) return env @@ -221,11 +227,15 @@ def spawn_pty( cols: int = 120, rows: int = 40, ): + # pexpect changes the child directory but does not rewrite an inherited + # PWD. OpenCode resolves its initial project from PWD, so pin it to the + # project under test instead of the parent shell's repository directory. + child_env = {**env, "PWD": str(cwd)} child = pexpect.spawn( argv[0], argv[1:], cwd=str(run_cwd or cwd), - env=env, + env=child_env, encoding="utf-8", timeout=120, dimensions=(rows, cols), @@ -239,7 +249,9 @@ def wait_pattern(child: pexpect.spawn, pattern: str | re.Pattern, timeout: float if idx == 0: return child.before + child.after if idx == 2: - raise RuntimeError(f"{label}: process exited (code {child.exitstatus})") + output = strip_ansi(getattr(child, "before", "") or "") + child.close() + raise RuntimeError(f"{label}: process exited (code {child.exitstatus}); output: {output[-1000:]!r}") raise RuntimeError(f"{label}: timed out after {timeout}s") @@ -259,6 +271,8 @@ def screen(child: pexpect.spawn) -> str: def snapshot(child: pexpect.spawn) -> str: + if child.closed: + return strip_ansi(getattr(child, "before", "") or "") return strip_ansi((getattr(child, "before", "") or "") + screen(child)) @@ -479,9 +493,6 @@ def test_run_interactive_demo(report: QaReport, ws: Path) -> None: f"expected demo queue rejection or dock; tail: {buf[-400:]!r}", ) - child.send("/quit\r") - child.expect(pexpect.EOF, timeout=15) - report.ok("run -i --demo /quit exits") except Exception as exc: save_screen("run-demo-error", str(exc) + "\n" + snapshot(child)) report.fail(name, str(exc)) @@ -522,11 +533,8 @@ def test_run_interactive_mock( else: report.note("run mock queue key: dock text not seen; checking HTTP queue path next") - child.send("/quit\r") - child.expect(pexpect.EOF, timeout=20) - report.ok("run -i mock /quit exits") except Exception as exc: - save_screen("run-mock-error", str(exc)) + save_screen("run-mock-error", str(exc) + "\n" + snapshot(child)) report.fail("run -i mock queue flow", str(exc)) finally: close_child(child) @@ -605,8 +613,6 @@ def test_run_interactive_api_queue(report: QaReport, ws: Path, llm_url: str, cli ) save_screen("run-attach-queue-dock", snapshot(child)) report.ok("run -i attach shows queued prompts in UI") - child.send("/quit\r") - child.expect(pexpect.EOF, timeout=15) except Exception as exc: save_screen("run-attach-queue-dock-error", snapshot(child)) if "timed out" in str(exc).lower(): @@ -654,7 +660,7 @@ def test_tui_full_queue( child.send("q") time.sleep(0.4) except Exception as exc: - save_screen("tui-full-error", str(exc)) + save_screen("tui-full-error", str(exc) + "\n" + snapshot(child)) report.fail("opencode-local full TUI", str(exc)) finally: close_child(child) @@ -720,10 +726,15 @@ def test_tui_attach_queue( send_submit(child) time.sleep(0.8) + # Saving an edit resumes the paused drain. A fast model can promote + # it before this check, so accept the updated text in either the + # remaining durable queue or the projected session history. edited = response_data(curl_json("GET", f"{base_url}/api/session/{sid}/queue", str(ws))) - if not isinstance(edited, list) or len(edited) != 3 or "-edited" not in queued_text(edited[0]): - raise RuntimeError(f"Enter did not save queued edit in place: {edited!r}") - report.ok("TUI Enter saves queued edit without sending it") + history = response_data(curl_json("GET", f"{base_url}/api/session/{sid}/message", str(ws))) + persisted = json.dumps({"queue": edited, "history": history}) + if "tui-q1-edited" not in persisted: + raise RuntimeError(f"Enter did not persist queued edit: queue={edited!r} history={history!r}") + report.ok("TUI Enter saves queued edit before resuming the drain") except Exception as exc: save_screen("tui-attach-queue-dock-error", snapshot(child)) if "timed out" in str(exc).lower(): @@ -759,8 +770,6 @@ def test_live_nemotron(report: QaReport, ws: Path) -> None: wait_pattern(child, re.compile(r"\bPING\b"), 120, "live nemotron PING reply") save_screen("live-nemotron-ping", snapshot(child)) report.ok(f"live model {MODEL_LIVE} returned PING") - child.send("/quit\r") - child.expect(pexpect.EOF, timeout=15) except Exception as exc: save_screen("live-nemotron-error", str(exc)) report.note(f"live Nemotron skipped/failed ({exc}); use OPENCODE_QA_LIVE=1 only when Zen is reachable") @@ -780,6 +789,7 @@ def main() -> int: try: llm_url = mock.start() + write_test_provider_config(ws, llm_url) print(f"mock LLM: {llm_url}") print("\n=== Phase 1: run --interactive --demo ===") diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 839a1b9b1020..d8130c6ba6e7 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -34,6 +34,10 @@ import { type ModelInput = Parameters[0]["model"] +export function isInteractiveRun(input: { mini?: boolean; interactive?: boolean }) { + return input.mini || input.interactive +} + function pick(value: string | undefined): ModelInput | undefined { if (!value) return undefined const [providerID, ...rest] = value.split("/") @@ -276,7 +280,7 @@ export const RunCommand = effectCmd({ const localInstance = yield* InstanceRef yield* Effect.promise(async () => { const rawMessage = [...args.message, ...(args["--"] || [])].join(" ") - const interactive = args.mini + const interactive = isInteractiveRun(args) const auto = args.auto || args.yolo || args["dangerously-skip-permissions"] const thinking = interactive ? (args.thinking ?? true) : (args.thinking ?? false) const die = (message: string): never => { @@ -299,7 +303,7 @@ export const RunCommand = effectCmd({ die("--mini cannot be used with --command") } - if (interactive && args._?.[0] !== "mini") { + if (args.mini && args._?.[0] !== "mini") { die("--mini must be used without the run subcommand") } @@ -459,7 +463,7 @@ export const RunCommand = effectCmd({ return message.slice(0, 50) + (message.length > 50 ? "..." : "") } - async function session(sdk: OpencodeClient): Promise { + async function session(sdk: OpencodeClient, agent?: string): Promise { if (args.session) { const current = await sdk.session .get({ @@ -522,9 +526,16 @@ export const RunCommand = effectCmd({ } const name = title() + const model = pick(args.model) const result = await sdk.session.create({ title: name, permission: [...rules], + agent, + model: model && { + providerID: model.providerID, + id: model.modelID, + ...(args.variant === undefined ? {} : { variant: args.variant }), + }, }) const id = result.data?.id if (!id) { @@ -577,7 +588,9 @@ export const RunCommand = effectCmd({ } async function localAgent() { - if (!args.agent) return undefined + if (!args.agent) { + return Effect.runPromise(agentSvc.defaultAgent().pipe(Effect.provideService(InstanceRef, localInstance))) + } const name = args.agent const entry = await Effect.runPromise( @@ -643,8 +656,8 @@ export const RunCommand = effectCmd({ } async function pickAgent(sdk: OpencodeClient) { - if (!args.agent) return undefined if (args.attach) { + if (!args.agent) return undefined return attachAgent(sdk) } diff --git a/packages/opencode/src/cli/cmd/run/footer.prompt.tsx b/packages/opencode/src/cli/cmd/run/footer.prompt.tsx index 8fe9c34188c7..75831b18b2bf 100644 --- a/packages/opencode/src/cli/cmd/run/footer.prompt.tsx +++ b/packages/opencode/src/cli/cmd/run/footer.prompt.tsx @@ -90,6 +90,7 @@ export type PromptState = { rows: Accessor requestExit: () => boolean onSubmit: () => void + onQueue: () => void submitText: (text: string) => void openEditor: (input?: { value?: string }) => Promise onKeyDown: (event: KeyEvent) => void @@ -1222,6 +1223,11 @@ export function createPromptState(input: PromptInput): PromptState { submitPrompt(clonePrompt(draft)) } + const onQueue = () => { + syncDraft() + submitPrompt({ ...clonePrompt(draft), queued: true }) + } + const submitText = (text: string) => { submitPrompt({ text, parts: [] }) } @@ -1291,6 +1297,7 @@ export function createPromptState(input: PromptInput): PromptState { rows: menu.rows, requestExit, onSubmit, + onQueue, submitText, openEditor, onKeyDown, diff --git a/packages/opencode/src/cli/cmd/run/footer.view.tsx b/packages/opencode/src/cli/cmd/run/footer.view.tsx index d01de60995c3..c42d633276c9 100644 --- a/packages/opencode/src/cli/cmd/run/footer.view.tsx +++ b/packages/opencode/src/cli/cmd/run/footer.view.tsx @@ -546,6 +546,20 @@ export function RunFooterView(props: RunFooterViewProps) { ], })) + useBindings(() => ({ + mode: OPENCODE_BASE_MODE, + enabled: active().type === "prompt" && route().type === "composer" && !composer.visible(), + commands: [ + { + name: "input.queue", + title: "Queue prompt", + category: "Prompt", + run: composer.onQueue, + }, + ], + bindings: props.tuiConfig.keybinds.get("input.queue"), + })) + useBindings(() => ({ mode: OPENCODE_BASE_MODE, enabled: active().type === "prompt" && route().type === "composer" && foregroundSubagents(), diff --git a/packages/opencode/src/cli/cmd/run/runtime.ts b/packages/opencode/src/cli/cmd/run/runtime.ts index c42cb34e4928..4435de412b03 100644 --- a/packages/opencode/src/cli/cmd/run/runtime.ts +++ b/packages/opencode/src/cli/cmd/run/runtime.ts @@ -96,7 +96,7 @@ type RunLocalInput = { directory: string fetch: typeof globalThis.fetch resolveAgent: () => Promise - session: (sdk: RunInput["sdk"]) => Promise<{ id: string; title?: string } | undefined> + session: (sdk: RunInput["sdk"], agent?: string) => Promise<{ id: string; title?: string } | undefined> share: (sdk: RunInput["sdk"], sessionID: string) => Promise agent: RunInput["agent"] model: RunInput["model"] @@ -935,7 +935,8 @@ export async function runInteractiveLocalMode(input: RunLocalInput): Promise { + session = input.resolveAgent().then(async (agent) => { + const next = await input.session(sdk, agent) if (!next?.id) { throw new Error("Session not found") } diff --git a/packages/opencode/src/cli/cmd/run/stream.current.ts b/packages/opencode/src/cli/cmd/run/stream.current.ts index 3872249a416c..df26461d19b9 100644 --- a/packages/opencode/src/cli/cmd/run/stream.current.ts +++ b/packages/opencode/src/cli/cmd/run/stream.current.ts @@ -420,7 +420,7 @@ function currentEventKey(event: SessionsEventsOutput) { async function latestSequence(client: CurrentClient, sessionID: string) { let after: number | undefined while (true) { - const page = await client.sessions.history({ sessionID, after, limit: 1000 }) + const page = await client.sessions.history({ sessionID, after, limit: 100 }) const next = page.data.at(-1)?.durable?.seq if (!page.hasMore || next === undefined) return next after = next @@ -528,6 +528,8 @@ export async function createCurrentSessionTransport(input: StreamInput): Promise await publish(event) } const active = await input.client.sessions.active() + const queued = await input.listQueue?.().catch(() => undefined) + if (queued) input.footer.event({ type: "queue", queue: queued.length, queued }) input.footer.event({ type: "stream.patch", patch: { diff --git a/packages/opencode/src/share/current.ts b/packages/opencode/src/share/current.ts index 617501475bcd..f3b7c040a041 100644 --- a/packages/opencode/src/share/current.ts +++ b/packages/opencode/src/share/current.ts @@ -1,6 +1,8 @@ export * as SessionSharingCurrent from "./current" import { Config } from "@/config/config" +import { InstanceRef } from "@/effect/instance-ref" +import { InstanceStore } from "@/project/instance-store" import { Database } from "@opencode-ai/core/database/database" import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { EventV2 } from "@opencode-ai/core/event" @@ -30,15 +32,19 @@ const layer = Layer.effect( const { db } = yield* Database.Service const events = yield* EventV2.Service const sessions = yield* SessionV2.Service + const instances = yield* InstanceStore.Service const transport = yield* ShareTransport.Service const sync = Effect.fn("SessionSharingCurrent.sync")(function* (sessionID: SessionV2.ID) { const info = yield* sessions.get(sessionID) const messages = yield* sessions.messages({ sessionID, order: "asc" }) - yield* transport.sync(sessionID, [ - { type: "session", data: encodeInfo(info) }, - ...messages.map((message) => ({ type: "message" as const, data: shareMessage(message) })), - ]) + const instance = (yield* InstanceRef) ?? (yield* instances.load({ directory: info.location.directory })) + yield* transport + .sync(sessionID, [ + { type: "session", data: encodeInfo(info) }, + ...messages.map((message) => ({ type: "message" as const, data: shareMessage(message) })), + ]) + .pipe(Effect.provideService(InstanceRef, instance)) }) const unsubscribe = yield* events.listen((event) => { @@ -90,5 +96,5 @@ const layer = Layer.effect( export const node = LayerNode.make({ service: SessionSharing.Service, layer, - deps: [Config.node, Database.node, EventV2.node, ShareTransport.node, SessionV2.node], + deps: [Config.node, Database.node, EventV2.node, ShareTransport.node, SessionV2.node, InstanceStore.node], }) diff --git a/packages/opencode/test/cli/run/footer.view.test.tsx b/packages/opencode/test/cli/run/footer.view.test.tsx index d6f9c516285e..e6082799cf0c 100644 --- a/packages/opencode/test/cli/run/footer.view.test.tsx +++ b/packages/opencode/test/cli/run/footer.view.test.tsx @@ -741,6 +741,28 @@ test("direct footer keeps leader variant binding inactive when leader is disable } }) +test("direct footer queues the draft through the configured queue binding", async () => { + const submits: RunPrompt[] = [] + const app = await renderFooter({ + tuiConfig: createTuiResolvedConfig({ keybinds: { input_queue: "ctrl+shift+enter" } }), + onSubmit(prompt) { + submits.push(prompt) + return true + }, + }) + + try { + await app.renderOnce() + "queued draft".split("").forEach((key) => app.mockInput.pressKey(key)) + app.mockInput.pressKey("RETURN", { ctrl: true, shift: true }) + await app.renderOnce() + + expect(submits).toEqual([{ text: "queued draft", parts: [], queued: true }]) + } finally { + app.cleanup() + } +}) + test("direct footer submits slash autocomplete selections without dispatching shell completions", async () => { const submits: RunPrompt[] = [] const app = await renderFooter({ diff --git a/packages/opencode/test/cli/run/interactive-mode.test.ts b/packages/opencode/test/cli/run/interactive-mode.test.ts new file mode 100644 index 000000000000..1158e3e8c0c9 --- /dev/null +++ b/packages/opencode/test/cli/run/interactive-mode.test.ts @@ -0,0 +1,10 @@ +import { expect, test } from "bun:test" +import { isInteractiveRun } from "@/cli/cmd/run" + +test("run interactive mode accepts the public interactive flag", () => { + expect(isInteractiveRun({ interactive: true })).toBe(true) +}) + +test("run mini mode remains interactive", () => { + expect(isInteractiveRun({ mini: true })).toBe(true) +}) diff --git a/packages/opencode/test/cli/run/stream.current.test.ts b/packages/opencode/test/cli/run/stream.current.test.ts index f4be03556c8a..30bb3264b177 100644 --- a/packages/opencode/test/cli/run/stream.current.test.ts +++ b/packages/opencode/test/cli/run/stream.current.test.ts @@ -252,6 +252,15 @@ describe("current interactive session presentation", () => { thinking: true, limits: () => ({}), footer: ui.api, + listQueue: async () => [{ id: "msg_queued", text: "queued before attach" }], + }) + const history = requests.find((item) => new URL(item.url).pathname.endsWith("/history")) + expect(history).toBeDefined() + expect(new URL(history!.url).searchParams.get("limit")).toBe("100") + expect(ui.events).toContainEqual({ + type: "queue", + queue: 1, + queued: [{ id: "msg_queued", text: "queued before attach" }], }) const prompt = { text: "hello", diff --git a/packages/opencode/test/share/current.test.ts b/packages/opencode/test/share/current.test.ts index dabbb8eebfdb..9fc1297f1eea 100644 --- a/packages/opencode/test/share/current.test.ts +++ b/packages/opencode/test/share/current.test.ts @@ -19,6 +19,8 @@ import { SessionSharing } from "@opencode-ai/core/session/share" import { SessionStore } from "@opencode-ai/core/session/store" import { DateTime, Effect, Layer } from "effect" import { Config } from "@/config/config" +import { InstanceBootstrap } from "@/project/bootstrap" +import { InstanceStore } from "@/project/instance-store" import { SessionSharingCurrent } from "@/share/current" import { ShareTransport } from "@/share/transport" import { resetDatabase } from "../fixture/db" @@ -68,6 +70,7 @@ const projects = Layer.succeed( commit: () => Effect.void, }), ) +const bootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void })) const it = testEffect( AppNodeBuilder.build( LayerNode.group([ @@ -83,6 +86,7 @@ const it = testEffect( [ProjectV2.node, projects], [SessionExecution.node, SessionExecution.noopLayer], [ShareTransport.node, transport], + [InstanceStore.bootstrapNode, bootstrap], ], ), ) From 7a8198e3e99713656212578ee3c7806849d727b2 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sun, 2 Aug 2026 03:56:48 -0300 Subject: [PATCH 003/129] fix(session): preserve prompt projection metadata --- .../src/pages/session/current/reducer.test.ts | 55 +++++++++++++++++++ packages/core/src/session/runner/llm.ts | 5 ++ packages/core/test/session-runner.test.ts | 41 +++++++++++++- .../src/v2/components/prompt-input/index.tsx | 7 +-- 4 files changed, 102 insertions(+), 6 deletions(-) diff --git a/packages/app/src/pages/session/current/reducer.test.ts b/packages/app/src/pages/session/current/reducer.test.ts index 1b36ffd782e7..ee28e9cb4392 100644 --- a/packages/app/src/pages/session/current/reducer.test.ts +++ b/packages/app/src/pages/session/current/reducer.test.ts @@ -150,6 +150,61 @@ describe("current session reducer", () => { ]) }) + test("keeps a promoted user prompt visible when its provider step fails", () => { + const state = dispatch([ + { type: "hydrated", sequence: 10, messages: [] }, + { + type: "event", + event: decodeEvent({ + id: "evt_prompted", + type: "session.next.prompted", + durable: { aggregateID: "ses_test", seq: 11, version: 1 }, + data: { + timestamp: 11, + sessionID: "ses_test", + messageID: "msg_hi", + prompt: { text: "hi" }, + delivery: "steer", + }, + }), + }, + { + type: "event", + event: decodeEvent({ + id: "evt_started", + type: "session.next.step.started", + durable: { aggregateID: "ses_test", seq: 12, version: 1 }, + data: { + timestamp: 12, + sessionID: "ses_test", + assistantMessageID: "msg_error", + agent: "build", + model: { providerID: "opencode", id: "mimo-v2.5-free" }, + }, + }), + }, + { + type: "event", + event: decodeEvent({ + id: "evt_failed", + type: "session.next.step.failed", + durable: { aggregateID: "ses_test", seq: 13, version: 2 }, + data: { + timestamp: 13, + sessionID: "ses_test", + assistantMessageID: "msg_error", + error: { type: "unknown", message: "Provider unavailable" }, + }, + }), + }, + ]) + + expect(state.messages).toMatchObject([ + { id: "msg_hi", type: "user", text: "hi" }, + { id: "msg_error", type: "assistant", finish: "error", error: { message: "Provider unavailable" } }, + ]) + }) + test("replaces a projected message in place and keeps timeline order", () => { const state = dispatch([ { diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index 2004a0b6b8c8..9668ee952139 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -521,6 +521,10 @@ const layer = Layer.effect( if (yield* compaction.compactIfNeeded({ sessionID: session.id, entries, model, request })) return yield* Effect.die(continueAfterCompaction(currentStep)) const startSnapshot = yield* snapshots.capture() + const hasPreparedContext = history.some( + (message) => + message.type === "assistant" && (message.systemPrompt !== undefined || message.toolDefinitions !== undefined), + ) const publisher = createLLMEventPublisher(events, { sessionID: session.id, agent: agent.id, @@ -538,6 +542,7 @@ const layer = Layer.effect( const providerStream = llm .stream(request, { onPrepared: (prepared) => { + if (hasPreparedContext) return publisher.setPreparedContext({}) const systemPrompt = prepared.metadata?.systemPrompt const toolDefinitions = prepared.metadata?.toolDefinitions return publisher.setPreparedContext({ diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 80a64b508d33..4ec569fe26cc 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -1798,8 +1798,6 @@ describe("SessionRunnerLLM", () => { type: "assistant", agent: "reviewer", model: { id: "replacement", variant: "thinking" }, - systemPrompt, - toolDefinitions, finish: "stop", content: [{ type: "text", text: '{"answer":"done"}' }], }, @@ -1822,6 +1820,12 @@ describe("SessionRunnerLLM", () => { expect(eventTypes.indexOf("session.next.tool.success")).toBeLessThan( eventTypes.lastIndexOf("session.next.step.ended"), ) + const assistant = (yield* session.context(sessionID)).filter( + (message): message is SessionMessage.Assistant => message.type === "assistant", + ) + expect(assistant[1]).toMatchObject({ systemPrompt, toolDefinitions }) + expect(assistant[2]).not.toHaveProperty("systemPrompt") + expect(assistant[2]).not.toHaveProperty("toolDefinitions") }), ) @@ -2293,6 +2297,39 @@ describe("SessionRunnerLLM", () => { }), ) + it.effect("projects a steer submitted during a failed provider turn", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false }) + + requests.length = 0 + responses = [ + [LLMEvent.providerError({ message: "Provider unavailable" })], + [LLMEvent.providerError({ message: "Provider unavailable" })], + ] + streamGate = yield* Deferred.make() + streamStarted = yield* Deferred.make() + + const first = yield* session.resume(sessionID).pipe(Effect.forkChild) + yield* Deferred.await(streamStarted) + yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Render this prompt" }) }) + yield* Deferred.succeed(streamGate, undefined) + yield* Fiber.join(first) + while (requests.length < 2) yield* Effect.yieldNow + streamGate = undefined + streamStarted = undefined + + expect(requests).toHaveLength(2) + expect((yield* session.context(sessionID)).map((message) => [message.type, message.type === "user" && message.text])).toEqual([ + ["user", "Start working"], + ["assistant", false], + ["user", "Render this prompt"], + ["assistant", false], + ]) + }), + ) + it.effect("promotes queued input after continuation ends", () => Effect.gen(function* () { yield* setup diff --git a/packages/session-ui/src/v2/components/prompt-input/index.tsx b/packages/session-ui/src/v2/components/prompt-input/index.tsx index 985f17ca885b..934597e0e350 100644 --- a/packages/session-ui/src/v2/components/prompt-input/index.tsx +++ b/packages/session-ui/src/v2/components/prompt-input/index.tsx @@ -261,16 +261,15 @@ export function PromptInputV2(props: PromptInputV2Props) { } > - } aria-pressed={queue().active()} aria-label={queue().label()} onClick={queue().onToggle} - > - {queue().label()} - + /> )} From 7b824b34ff036edeb332bda840077d3afec00fb8 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sun, 2 Aug 2026 04:28:49 -0300 Subject: [PATCH 004/129] fix(session-ui): restore queue icon --- packages/core/test/session-runner.test.ts | 28 ++++++++++++------- .../src/v2/components/prompt-input/index.tsx | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 4ec569fe26cc..1abcb47e3c27 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -2304,21 +2304,29 @@ describe("SessionRunnerLLM", () => { yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false }) requests.length = 0 - responses = [ - [LLMEvent.providerError({ message: "Provider unavailable" })], - [LLMEvent.providerError({ message: "Provider unavailable" })], + const gate = yield* Deferred.make() + const started = yield* Deferred.make() + const failure = providerUnavailable() + response = [ + LLMEvent.stepStart({ index: 0 }), + LLMEvent.stepFinish({ index: 0, reason: "stop" }), + LLMEvent.finish({ reason: "stop" }), ] - streamGate = yield* Deferred.make() - streamStarted = yield* Deferred.make() + responseStream = Stream.unwrap( + Effect.gen(function* () { + yield* Deferred.succeed(started, undefined) + yield* Deferred.await(gate) + return Stream.fail(failure) + }), + ) const first = yield* session.resume(sessionID).pipe(Effect.forkChild) - yield* Deferred.await(streamStarted) + yield* Deferred.await(started) yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Render this prompt" }) }) - yield* Deferred.succeed(streamGate, undefined) - yield* Fiber.join(first) + yield* Deferred.succeed(gate, undefined) + expect(yield* Fiber.await(first)).toMatchObject({ _tag: "Failure" }) while (requests.length < 2) yield* Effect.yieldNow - streamGate = undefined - streamStarted = undefined + while ((yield* session.context(sessionID)).length < 4) yield* Effect.yieldNow expect(requests).toHaveLength(2) expect((yield* session.context(sessionID)).map((message) => [message.type, message.type === "user" && message.text])).toEqual([ diff --git a/packages/session-ui/src/v2/components/prompt-input/index.tsx b/packages/session-ui/src/v2/components/prompt-input/index.tsx index 934597e0e350..d323b32f7a07 100644 --- a/packages/session-ui/src/v2/components/prompt-input/index.tsx +++ b/packages/session-ui/src/v2/components/prompt-input/index.tsx @@ -265,7 +265,7 @@ export function PromptInputV2(props: PromptInputV2Props) { type="button" variant="ghost-muted" size="normal" - icon={} + icon={} aria-pressed={queue().active()} aria-label={queue().label()} onClick={queue().onToggle} From 7570ca766c54eba4532c09e5418060afa5762051 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sun, 2 Aug 2026 15:31:27 -0300 Subject: [PATCH 005/129] fix(app): restore compatibility protocol detection --- .../session/session-context-system.test.ts | 4 +- .../session/session-context-system.ts | 2 +- .../app/src/context/global-sync/utils.test.ts | 17 ++++++++ packages/app/src/context/global-sync/utils.ts | 42 ++++++++++--------- .../app/src/utils/server-protocol.test.ts | 2 +- 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/packages/app/src/components/session/session-context-system.test.ts b/packages/app/src/components/session/session-context-system.test.ts index 3180f5fdb591..8a6a9275d68f 100644 --- a/packages/app/src/components/session/session-context-system.test.ts +++ b/packages/app/src/components/session/session-context-system.test.ts @@ -93,8 +93,8 @@ describe("getSessionSystemPrompt", () => { ] expect(getSessionPreparedContext(messages, { messageID: "msg_04" })).toEqual({ - systemPrompt: "current user system", - toolDefinitions: undefined, + systemPrompt: "first prepared system", + toolDefinitions: "first tools", }) }) }) diff --git a/packages/app/src/components/session/session-context-system.ts b/packages/app/src/components/session/session-context-system.ts index 597185612964..7b245d8d402a 100644 --- a/packages/app/src/components/session/session-context-system.ts +++ b/packages/app/src/components/session/session-context-system.ts @@ -25,7 +25,7 @@ export function getSessionPreparedContext( const active = selectSessionContextMessages(messages, input.revert) const selected = input.messageID ? active.find((message) => message.id === input.messageID) : undefined const assistant = - selected?.type === "assistant" + selected?.type === "assistant" && Boolean(selected.systemPrompt?.trim() || selected.toolDefinitions?.trim()) ? selected : active.findLast( (message): message is SessionMessage.Assistant => diff --git a/packages/app/src/context/global-sync/utils.test.ts b/packages/app/src/context/global-sync/utils.test.ts index 69ca494992b0..3c661b64cf7a 100644 --- a/packages/app/src/context/global-sync/utils.test.ts +++ b/packages/app/src/context/global-sync/utils.test.ts @@ -41,6 +41,23 @@ describe("normalizeAgentList", () => { }, ]) }) + + test("accepts current agents without optional request settings", () => { + const result = normalizeAgentList([ + { + id: "general", + name: "General", + mode: "primary", + hidden: false, + request: { headers: {}, body: {} }, + permissions: [], + }, + ] as unknown as AgentListOutput["data"]) + + expect(result[0]?.options).toEqual({}) + expect(result[0]?.temperature).toBeUndefined() + expect(result[0]?.topP).toBeUndefined() + }) }) describe("normalizePermissionRequest", () => { diff --git a/packages/app/src/context/global-sync/utils.ts b/packages/app/src/context/global-sync/utils.ts index 59632e53c92e..953370ac7778 100644 --- a/packages/app/src/context/global-sync/utils.ts +++ b/packages/app/src/context/global-sync/utils.ts @@ -14,26 +14,28 @@ export const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0) export function normalizeAgentList(input: AgentListOutput["data"] | Agent[]): Agent[] { if (input.every((agent) => !("request" in agent))) return input as Agent[] - return (input as AgentListOutput["data"]).map((agent) => ({ - name: agent.id, - description: agent.description, - mode: agent.mode, - hidden: agent.hidden, - temperature: - typeof agent.request.settings.temperature === "number" ? agent.request.settings.temperature : undefined, - topP: typeof agent.request.settings.topP === "number" ? agent.request.settings.topP : undefined, - color: agent.color, - permission: agent.permissions.map((rule) => ({ - permission: rule.action, - pattern: rule.resource, - action: rule.effect, - })), - model: agent.model && { providerID: agent.model.providerID, modelID: agent.model.id }, - variant: agent.model?.variant, - prompt: agent.system, - options: agent.request.settings, - steps: agent.steps, - })) + return (input as AgentListOutput["data"]).map((agent) => { + const settings = agent.request.settings ?? {} + return { + name: agent.id, + description: agent.description, + mode: agent.mode, + hidden: agent.hidden, + temperature: typeof settings.temperature === "number" ? settings.temperature : undefined, + topP: typeof settings.topP === "number" ? settings.topP : undefined, + color: agent.color, + permission: agent.permissions.map((rule) => ({ + permission: rule.action, + pattern: rule.resource, + action: rule.effect, + })), + model: agent.model && { providerID: agent.model.providerID, modelID: agent.model.id }, + variant: agent.model?.variant, + prompt: agent.system, + options: settings, + steps: agent.steps, + } + }) } export function normalizePermissionRequest(input: PermissionV2Request | PermissionRequest): PermissionRequest { diff --git a/packages/app/src/utils/server-protocol.test.ts b/packages/app/src/utils/server-protocol.test.ts index 2130a968c4bc..9cd309b75a33 100644 --- a/packages/app/src/utils/server-protocol.test.ts +++ b/packages/app/src/utils/server-protocol.test.ts @@ -31,7 +31,7 @@ describe("detectServerProtocol", () => { test("recognizes the transitional V1 API health response", async () => { const fetcher = mockFetch((input) => { const path = new URL(input instanceof Request ? input.url : input).pathname - if (path === "/global/health") return Promise.resolve(json({}, 404)) + if (path === "/api/health") return Promise.resolve(json({}, 404)) return Promise.resolve(json({ healthy: true })) }) From 771cb9896e108f83d45b374d142988a9cb77a770 Mon Sep 17 00:00:00 2001 From: henry701 Date: Mon, 3 Aug 2026 00:47:11 -0300 Subject: [PATCH 006/129] fix(session): restore live projection and accounting --- build-local.sh | 9 +- packages/app/src/app.tsx | 4 +- .../session/session-context-metrics.test.ts | 2 +- .../session/session-context-metrics.ts | 12 +- packages/app/src/pages/directory-layout.tsx | 3 +- packages/app/src/pages/session.tsx | 36 +- .../src/pages/session/current/model.test.ts | 52 +- .../app/src/pages/session/current/model.ts | 66 +- .../src/pages/session/current/reducer.test.ts | 28 + .../app/src/pages/session/current/reducer.ts | 5 + .../session/timeline/message-timeline.tsx | 14 +- .../app/src/pages/session/timeline/model.ts | 38 +- packages/app/src/utils/server-compat.test.ts | 17 +- packages/app/src/utils/server-compat.ts | 57 +- .../app/src/utils/server-protocol.test.ts | 17 +- packages/app/src/utils/server-protocol.ts | 9 + .../app/src/utils/session-message.test.ts | 148 +- packages/app/src/utils/session-message.ts | 91 +- .../client/src/generated-effect/client.ts | 7 +- packages/client/src/generated/client.ts | 14 + packages/client/src/generated/types.ts | 59 + packages/core/src/agent.ts | 2 +- packages/core/src/session/input.ts | 23 +- packages/core/src/session/message-updater.ts | 3 +- packages/core/test/agent.test.ts | 18 + packages/core/test/session-prompt.test.ts | 17 + packages/llm/src/protocols/openai-chat.ts | 8 +- .../provider/openai-compatible-chat.test.ts | 27 +- .../routes/instance/httpapi/groups/project.ts | 34 + .../instance/httpapi/handlers/project.ts | 20 + .../instance/httpapi/handlers/session.ts | 16 +- .../opencode/src/session/prepared-context.ts | 22 + packages/opencode/src/session/processor.ts | 15 +- .../test/server/httpapi-exercise/index.ts | 27 +- .../test/server/session-messages.test.ts | 1 + .../test/session/prepared-context.test.ts | 36 + packages/opencode/test/session/prompt.test.ts | 9 + packages/protocol/src/groups/model.ts | 15 + packages/sdk/js/src/v2/gen/sdk.gen.ts | 5369 +++++++++-------- packages/sdk/js/src/v2/gen/types.gen.ts | 166 +- packages/server/src/handlers/message.ts | 16 +- packages/server/src/handlers/model.ts | 22 +- .../server/src/handlers/prepared-context.ts | 16 + packages/server/src/handlers/session.ts | 130 +- packages/server/test/prepared-context.test.ts | 51 + 45 files changed, 3977 insertions(+), 2774 deletions(-) create mode 100644 packages/opencode/src/session/prepared-context.ts create mode 100644 packages/opencode/test/session/prepared-context.test.ts create mode 100644 packages/server/src/handlers/prepared-context.ts create mode 100644 packages/server/test/prepared-context.test.ts diff --git a/build-local.sh b/build-local.sh index de39d8c61a2b..ff3b4a194785 100755 --- a/build-local.sh +++ b/build-local.sh @@ -7,6 +7,7 @@ PKG_DIR="$REPO_ROOT/packages/opencode" INSTALL_DIR="$HOME/.local/bin" BINARY_NAME="opencode-local" SERVICE_NAME="opencode-server" +BACKEND_SERVICE_NAME="opencode-backend" RESTART_SERVICE=1 usage() { @@ -86,11 +87,11 @@ if [[ "$RESTART_SERVICE" == "0" ]]; then exit 0 fi -echo "==> Reloading systemd user daemon and restarting $SERVICE_NAME..." +echo "==> Reloading systemd user daemon and restarting $BACKEND_SERVICE_NAME and $SERVICE_NAME..." if XDG_RUNTIME_DIR="/run/user/$(id -u)" systemctl --user daemon-reload 2>/dev/null; then - XDG_RUNTIME_DIR="/run/user/$(id -u)" systemctl --user restart "$SERVICE_NAME" + XDG_RUNTIME_DIR="/run/user/$(id -u)" systemctl --user restart "$BACKEND_SERVICE_NAME" "$SERVICE_NAME" sleep 1 - XDG_RUNTIME_DIR="/run/user/$(id -u)" systemctl --user status "$SERVICE_NAME" --no-pager + XDG_RUNTIME_DIR="/run/user/$(id -u)" systemctl --user status "$BACKEND_SERVICE_NAME" "$SERVICE_NAME" --no-pager else echo " DBUS unavailable - simulating start on a different port to verify binary works..." TEST_PORT=14097 @@ -106,7 +107,7 @@ else exit 1 fi echo "" - echo " NOTE: run 'systemctl --user restart $SERVICE_NAME' from your desktop session to apply." + echo " NOTE: run 'systemctl --user restart $BACKEND_SERVICE_NAME $SERVICE_NAME' from your desktop session to apply." fi echo "==> Done." diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index f47c432e4206..d76d999125db 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -32,7 +32,6 @@ import { ErrorBoundary, For, type JSX, - lazy, onCleanup, type ParentProps, Show, @@ -69,8 +68,7 @@ import { createSessionLineage } from "@/pages/session/session-lineage" import { SessionPage, SessionRouteErrorBoundary, TargetSessionRouteContent } from "@/pages/session" import { NewHome } from "@/pages/home" import { LegacyHome } from "@/pages/home/legacy-home" - -const NewSession = lazy(() => import("@/pages/new-session")) +import NewSession from "@/pages/new-session" const SessionRoute = () => { const settings = useSettings() diff --git a/packages/app/src/components/session/session-context-metrics.test.ts b/packages/app/src/components/session/session-context-metrics.test.ts index cce60f2253c0..ba60e7863f47 100644 --- a/packages/app/src/components/session/session-context-metrics.test.ts +++ b/packages/app/src/components/session/session-context-metrics.test.ts @@ -64,7 +64,7 @@ describe("getSessionContext", () => { expect(ctx?.message.id).toBe("a2" as never) expect(ctx?.total).toBe(500) - expect(ctx?.input).toBe(300) + expect(ctx?.input).toBe(350) expect(ctx?.usage).toBe(50) expect(ctx?.providerLabel).toBe("OpenAI") expect(ctx?.modelLabel).toBe("GPT-4.1") diff --git a/packages/app/src/components/session/session-context-metrics.ts b/packages/app/src/components/session/session-context-metrics.ts index a9d00d7679bb..68b25604a4fa 100644 --- a/packages/app/src/components/session/session-context-metrics.ts +++ b/packages/app/src/components/session/session-context-metrics.ts @@ -40,10 +40,7 @@ const lastAssistantWithTokens = (messages: readonly SessionMessage.Message[]) => } } -const build = ( - messages: readonly SessionMessage.Message[] = [], - providers: Provider[] = [], -): Context | undefined => { +const build = (messages: readonly SessionMessage.Message[] = [], providers: Provider[] = []): Context | undefined => { const message = lastAssistantWithTokens(messages) if (!message?.tokens) return @@ -59,15 +56,12 @@ const build = ( providerLabel: provider?.name ?? message.model.providerID, modelLabel: model?.name ?? message.model.id, limit, - input: message.tokens.input, + input: message.tokens.input + message.tokens.cache.read + message.tokens.cache.write, total, usage: limit ? Math.round((total / limit) * 100) : null, } } -export function getSessionContext( - messages: readonly SessionMessage.Message[] = [], - providers: Provider[] = [], -) { +export function getSessionContext(messages: readonly SessionMessage.Message[] = [], providers: Provider[] = []) { return build(messages, providers) } diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx index 3f9adc49b03b..a0b357639655 100644 --- a/packages/app/src/pages/directory-layout.tsx +++ b/packages/app/src/pages/directory-layout.tsx @@ -46,8 +46,7 @@ export function DirectoryDataProvider( () => params.id, (id) => sync() - // Session page owns message paging via the current client; only warm session info here. - .session.sync(id, { skipMessages: true }) + .session.sync(id) .catch(() => {}), ) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 20d9d118e4ff..74158e31b113 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -523,7 +523,9 @@ export default function Page() { const current = useCurrentSession(() => params.id) const busy = (sessionID: string) => - sessionID === params.id ? current.busy() || sync().data.session_working(sessionID) : sync().data.session_working(sessionID) + sessionID === params.id + ? current.busy() || sync().data.session_working(sessionID) + : sync().data.session_working(sessionID) createEffect( on( () => [params.id, current.readiness(), current.busy()] as const, @@ -559,14 +561,27 @@ export default function Page() { const activeTab = tabState.activeTab const activeFileTab = tabState.activeFileTab const revertMessageID = createMemo(() => info()?.revert?.messageID) - const contextMessages = current.messages - const timeline = createTimelineModel({ sessionID: () => params.id, revertMessageID }) + const contextMessages = current.context + const timeline = createTimelineModel({ + sessionID: () => params.id, + revertMessageID, + current: { + enabled: () => serverSDK().protocolKind() === "v2", + messages: current.messages, + ready: () => current.readiness() === "ready", + more: current.hasOlder, + loading: () => current.readiness() === "loading" || current.loadingOlder(), + loadOlder: current.loadOlder, + }, + }) const historyLoading = timeline.history.loading const historyMore = timeline.history.more const lastUserMessage = timeline.lastUserMessage const messages = timeline.messages const messagesReady = timeline.ready const sessionSync = timeline.resource + const timelineSessionMessages = timeline.sessionMessages + const timelineParts = timeline.parts const userMessages = timeline.userMessages const visibleUserMessages = timeline.visibleUserMessages @@ -1723,7 +1738,13 @@ export default function Page() { })), ) const [editingFollowup, setEditingFollowup] = createSignal() - createEffect(on(() => params.id, () => setEditingFollowup(undefined), { defer: true })) + createEffect( + on( + () => params.id, + () => setEditingFollowup(undefined), + { defer: true }, + ), + ) const followupMutation = useMutation(() => ({ mutationFn: async (input: { sessionID: string; id: string; manual?: boolean }) => { @@ -1798,7 +1819,9 @@ export default function Page() { if (draft.queueID) setEditingFollowup(undefined) } - const followupDock = createMemo(() => queuedFollowups().map((item) => ({ id: item.id, text: followupText(item.draft) }))) + const followupDock = createMemo(() => + queuedFollowups().map((item) => ({ id: item.id, text: followupText(item.draft) })), + ) const sendFollowup = (sessionID: string, id: string, opts?: { manual?: boolean }) => { if (current.session()?.parentID) return Promise.resolve() @@ -2091,6 +2114,9 @@ export default function Page() { !location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled() } centered={centered()} + messages={messages} + sessionMessages={timelineSessionMessages} + getMessageParts={timelineParts} setContentRef={(el) => { content = el autoScroll.contentRef(el) diff --git a/packages/app/src/pages/session/current/model.test.ts b/packages/app/src/pages/session/current/model.test.ts index 0184a898fae6..585210b95d8f 100644 --- a/packages/app/src/pages/session/current/model.test.ts +++ b/packages/app/src/pages/session/current/model.test.ts @@ -1,9 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { - MessagesListOutput, - SessionsEventsOutput, - SessionsGetOutput, -} from "@/utils/current-client" +import type { MessagesListOutput, SessionsEventsOutput, SessionsGetOutput } from "@/utils/current-client" import { createRoot } from "solid-js" import { createCurrentSessionModel, type CurrentSessionPort } from "./model" @@ -20,7 +16,7 @@ const session: SessionsGetOutput = { const prompted = (seq: number) => ({ id: `evt_${seq}`, - type: "session.next.prompt.admitted", + type: "session.next.prompted", durable: { aggregateID: "ses_test", seq, version: 1 }, data: { timestamp: seq, @@ -98,6 +94,7 @@ describe("current session model", () => { .then(() => { expect(model.readiness()).toBe("ready") expect(model.lastEventSequence()).toBe(4) + expect(model.messages().map((message) => String(message.id))).toContain("msg_4") expect(queueReads).toBeGreaterThanOrEqual(2) model.dispose() dispose() @@ -189,6 +186,49 @@ describe("current session model", () => { }) }) }) + + test("refreshes context during manual refresh", async () => { + let contextReads = 0 + const base = makePort({ pages: [messages([], undefined, 0), messages([])] }) + const port = { + ...base, + sessions: { + ...base.sessions, + context: async () => { + contextReads++ + return [ + { + id: "msg_context", + type: "user" as const, + text: "context", + time: { created: 1 }, + }, + ] + }, + }, + } satisfies CurrentSessionPort + + await new Promise((resolve, reject) => { + createRoot((dispose) => { + const model = createCurrentSessionModel({ + sessionID: () => "ses_test", + client: () => port, + autoStart: false, + }) + model + .start() + .then(() => model.refresh()) + .then(() => { + expect(contextReads).toBe(2) + expect(model.context().map((message) => String(message.id))).toEqual(["msg_context"]) + model.dispose() + dispose() + resolve() + }) + .catch(reject) + }) + }) + }) }) async function until(predicate: () => boolean) { diff --git a/packages/app/src/pages/session/current/model.ts b/packages/app/src/pages/session/current/model.ts index be96d1492700..696b96b4c633 100644 --- a/packages/app/src/pages/session/current/model.ts +++ b/packages/app/src/pages/session/current/model.ts @@ -20,7 +20,8 @@ const decodeQueue = Schema.decodeUnknownSync(Schema.Array(SessionInput.Queued)) const decodeEvent = Schema.decodeUnknownSync(SessionEvent.All) export type CurrentSessionPort = { - readonly sessions: Pick + readonly sessions: Pick & + Partial> readonly messages: Pick } @@ -38,6 +39,7 @@ export function createCurrentSessionModel(input: { let sessionRefresh = 0 let queueRefresh = 0 let activeRefresh = 0 + let contextRefresh = 0 const dispatch = (action: CurrentSessionAction) => setState((current) => reduceCurrentSession(current, action)) @@ -71,6 +73,36 @@ export function createCurrentSessionModel(input: { }) } + const refreshContext = async (client: CurrentSessionPort, sessionID: string, signal?: AbortSignal) => { + const context = client.sessions.context + if (!context) return + const request = ++contextRefresh + try { + const messages = decodeMessages(await context({ sessionID }, { signal })) + if (request !== contextRefresh) return + dispatch({ type: "context-updated", context: messages }) + } catch (error) { + if (!isAbort(error, signal ?? new AbortController().signal)) + console.error("Failed to refresh current session context", error) + } + } + + const loadContext = async ( + client: CurrentSessionPort, + sessionID: string, + fallback: readonly SessionMessage.Message[], + signal?: AbortSignal, + ) => { + try { + if (!client.sessions.context) return fallback + return decodeMessages(await client.sessions.context({ sessionID }, { signal })) + } catch (error) { + if (!isAbort(error, signal ?? new AbortController().signal)) + console.error("Failed to load current session context", error) + return fallback + } + } + const newest = async (client: CurrentSessionPort, sessionID: string, signal?: AbortSignal) => { const page = await client.messages.list({ sessionID, order: "desc", limit: pageSize }, { signal }) return { @@ -89,10 +121,12 @@ export function createCurrentSessionModel(input: { const queue = events.some(eventRefreshesQueue) const session = events.some(eventRefreshesSession) const active = events.some(eventRefreshesActive) + const context = events.some(eventRefreshesContext) await Promise.all([ queue ? refreshQueue(client, sessionID, signal) : undefined, session ? refreshSession(client, sessionID, signal) : undefined, active ? refreshActive(client, sessionID, signal) : undefined, + context ? refreshContext(client, sessionID, signal) : undefined, ]) } @@ -151,6 +185,7 @@ export function createCurrentSessionModel(input: { const sessionID = input.sessionID() if (!sessionID) { dispatch({ type: "hydrated", messages: [] }) + dispatch({ type: "context-updated", context: [] }) return } const buffered: SessionEvent.Event[] = [] @@ -177,15 +212,17 @@ export function createCurrentSessionModel(input: { activeGeneration, ) await Promise.resolve() - const [session, queue, active] = await Promise.all([ + const [session, queue, active, context] = await Promise.all([ client.sessions.get({ sessionID }, { signal: controller.signal }).then(decodeSession), client.sessions.queueList({ sessionID }, { signal: controller.signal }).then(decodeQueue), client.sessions.active({ signal: controller.signal }).then((sessions) => sessions[sessionID] !== undefined), + loadContext(client, sessionID, page.messages, controller.signal), ]) if (controller.signal.aborted || generation !== activeGeneration) return dispatch({ type: "session-updated", session }) dispatch({ type: "queue-updated", queue }) dispatch({ type: "active-updated", active }) + dispatch({ type: "context-updated", context }) dispatch({ type: "hydrated", messages: page.messages, @@ -196,10 +233,14 @@ export function createCurrentSessionModel(input: { while (buffered.length > 0) { const events = buffered.splice(0) - events.forEach((event) => dispatch({ type: "event-observed", sequence: event.durable?.seq })) const page = await newest(client, sessionID, controller.signal) if (controller.signal.aborted || generation !== activeGeneration) return dispatch({ type: "newest-merged", messages: page.messages, hasOlder: page.cursor !== undefined }) + events.forEach((event) => dispatch({ type: "event", event })) + if (events.some((event) => event.type === "session.next.step.started")) + dispatch({ type: "active-updated", active: true }) + if (events.some((event) => event.type === "session.next.step.failed")) + dispatch({ type: "active-updated", active: false }) await refreshForEvents(client, sessionID, events, controller.signal) } buffering = false @@ -246,6 +287,7 @@ export function createCurrentSessionModel(input: { newest(client, sessionID, controller.signal), refreshQueue(client, sessionID, controller.signal), refreshActive(client, sessionID, controller.signal), + refreshContext(client, sessionID, controller.signal), ]) dispatch({ type: "newest-merged", messages: page.messages, hasOlder: page.cursor !== undefined }) } @@ -271,6 +313,7 @@ export function createCurrentSessionModel(input: { return { state, messages: () => state().messages, + context: () => state().context, queue: () => state().queue, session: () => state().session, active: () => state().active, @@ -321,6 +364,7 @@ function eventRefreshesQueue(event: SessionEvent.Event) { function eventRefreshesSession(event: SessionEvent.Event) { return ( + event.type === "session.next.updated" || event.type === "session.next.agent.switched" || event.type === "session.next.model.switched" || event.type === "session.next.moved" || @@ -339,6 +383,22 @@ function eventRefreshesActive(event: SessionEvent.Event) { ) } +function eventRefreshesContext(event: SessionEvent.Event) { + return ( + event.type === "session.next.message.imported" || + event.type === "session.next.prompted" || + event.type === "session.next.synthetic" || + event.type === "session.next.context.updated" || + event.type === "session.next.agent.switched" || + event.type === "session.next.model.switched" || + event.type === "session.next.step.started" || + event.type === "session.next.step.ended" || + event.type === "session.next.step.failed" || + event.type === "session.next.compaction.ended" || + event.type.startsWith("session.next.revert.") + ) +} + function isAbort(error: unknown, signal: AbortSignal) { return signal.aborted || (error instanceof DOMException && error.name === "AbortError") } diff --git a/packages/app/src/pages/session/current/reducer.test.ts b/packages/app/src/pages/session/current/reducer.test.ts index ee28e9cb4392..e9a0b2f3a072 100644 --- a/packages/app/src/pages/session/current/reducer.test.ts +++ b/packages/app/src/pages/session/current/reducer.test.ts @@ -150,6 +150,34 @@ describe("current session reducer", () => { ]) }) + test("does not duplicate messages when a buffered event is already in the hydrated page", () => { + const state = dispatch([ + { + type: "hydrated", + sequence: 10, + messages: [user("msg_hi", "hi", 9)], + }, + { + type: "event", + event: decodeEvent({ + id: "evt_prompted", + type: "session.next.prompted", + durable: { aggregateID: "ses_test", seq: 11, version: 1 }, + data: { + timestamp: 11, + sessionID: "ses_test", + messageID: "msg_hi", + prompt: { text: "hi" }, + delivery: "steer", + }, + }), + }, + ]) + + expect(state.messages).toHaveLength(1) + expect(state.messages[0]).toMatchObject({ id: "msg_hi", type: "user", text: "hi" }) + }) + test("keeps a promoted user prompt visible when its provider step fails", () => { const state = dispatch([ { type: "hydrated", sequence: 10, messages: [] }, diff --git a/packages/app/src/pages/session/current/reducer.ts b/packages/app/src/pages/session/current/reducer.ts index 476cbfba38d6..4b0f45be7adc 100644 --- a/packages/app/src/pages/session/current/reducer.ts +++ b/packages/app/src/pages/session/current/reducer.ts @@ -7,6 +7,7 @@ import { DateTime, Effect } from "effect" export type CurrentSessionState = { readonly messages: ReadonlyArray + readonly context: ReadonlyArray readonly queue: ReadonlyArray readonly session?: Session.Info readonly active: boolean @@ -28,6 +29,7 @@ export type CurrentSessionAction = readonly sequence?: number readonly cursor?: string } + | { readonly type: "context-updated"; readonly context: ReadonlyArray } | { readonly type: "older-loaded" readonly messages: ReadonlyArray @@ -51,6 +53,7 @@ export type CurrentSessionAction = export function currentSessionInitialState(): CurrentSessionState { return { messages: [], + context: [], queue: [], active: false, readiness: "loading", @@ -73,6 +76,8 @@ export function reduceCurrentSession(state: CurrentSessionState, action: Current hasOlder: action.cursor !== undefined, lastEventSequence: action.sequence, } + case "context-updated": + return { ...state, context: [...action.context] } case "older-loaded": { const loaded = new Set(state.messages.map((message) => message.id)) return { diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx index eca1541cea9d..a44521c5fc28 100644 --- a/packages/app/src/pages/session/timeline/message-timeline.tsx +++ b/packages/app/src/pages/session/timeline/message-timeline.tsx @@ -52,6 +52,7 @@ import type { ToolPart, UserMessage, } from "@opencode-ai/sdk/v2" +import type { SessionMessageInfo } from "@opencode-ai/client/promise" import { showToast } from "@/utils/toast" import { getDirectory, getFilename } from "@opencode-ai/core/util/path" import { Popover as KobaltePopover } from "@kobalte/core/popover" @@ -251,6 +252,9 @@ export function MessageTimeline(props: { onAutoScrollInteraction: (event: MouseEvent) => void shouldAnchorBottom: () => boolean centered: boolean + messages?: Accessor + sessionMessages?: Accessor + getMessageParts?: (messageID: string) => PartType[] setContentRef: (el: HTMLDivElement) => void userMessages: UserMessage[] anchor: (id: string) => string @@ -282,13 +286,17 @@ export function MessageTimeline(props: { if (!id) return idle return sync().data.session_status[id] ?? idle }) - const sessionMessages = createMemo(() => (sessionID() ? (sync().data.message[sessionID()!] ?? []) : [])) + const sessionMessages = createMemo(() => { + if (props.messages) return props.messages() + const id = sessionID() + return id ? (sync().data.message[id] ?? []) : [] + }) const projectedMessages = createMemo(() => { const id = sessionID() if (!id) return [] const visible = new Set(props.userMessages.map((message) => message.id)) const boundary = sessionMessages().find((message) => message.role === "user" && !visible.has(message.id))?.id - const messages = sync().data.session_message[id] ?? [] + const messages = props.sessionMessages?.() ?? sync().data.session_message[id] ?? [] return boundary ? messages.filter((message) => message.id < boundary) : messages }) const info = createMemo(() => { @@ -312,7 +320,7 @@ export function MessageTimeline(props: { return sync().data.message[id] ?? emptyMessages }) const parentTitle = createMemo(() => sessionTitle(parent()?.title) ?? language.t("command.session.new")) - const getMsgParts = (msgId: string) => sync().data.part[msgId] ?? emptyParts + const getMsgParts = (msgId: string) => props.getMessageParts?.(msgId) ?? sync().data.part[msgId] ?? emptyParts const getMsgPart = (messageID: string, partID: string) => getMsgParts(messageID).find((part) => part.id === partID) const childTaskDescription = createMemo(() => { const id = sessionID() diff --git a/packages/app/src/pages/session/timeline/model.ts b/packages/app/src/pages/session/timeline/model.ts index d178927cdd70..3eb1458096c8 100644 --- a/packages/app/src/pages/session/timeline/model.ts +++ b/packages/app/src/pages/session/timeline/model.ts @@ -1,8 +1,10 @@ import type { Message, UserMessage } from "@opencode-ai/sdk/v2" +import type { SessionMessage } from "@opencode-ai/schema/session-message" import { createMemo, createResource, onCleanup, untrack, type Accessor } from "solid-js" import { useServerSync } from "@/context/server-sync" import { useSync } from "@/context/sync" import { same } from "@/utils/same" +import { normalizeCurrentSessionMessages } from "@/utils/session-message" const emptyUserMessages: UserMessage[] = [] const sessionFreshness = 15_000 @@ -11,7 +13,8 @@ export function createTimelineModel(input: { sessionID: Accessor revertMessageID: Accessor current?: { - messages: Accessor + enabled: Accessor + messages: Accessor ready: Accessor more: Accessor loading: Accessor @@ -22,9 +25,18 @@ export function createTimelineModel(input: { const sync = useSync() let refreshFrame: number | undefined let refreshTimer: number | undefined + const useCurrent = () => input.current?.enabled() ?? false + const currentProjection = createMemo(() => { + const id = input.sessionID() + if (!id || !useCurrent()) return + return normalizeCurrentSessionMessages(id, input.current!.messages()) + }) const [resource] = createResource( - () => (input.current ? undefined : input.sessionID()), + () => { + const id = useCurrent() ? undefined : input.sessionID() + return id + }, (id) => { clearRefresh() if (!id) return @@ -47,12 +59,21 @@ export function createTimelineModel(input: { }, ) const messages = createMemo(() => { - if (input.current) return input.current.messages() + if (useCurrent()) return currentProjection()?.messages ?? [] const id = input.sessionID() return id ? (sync().data.message[id] ?? []) : [] }) + const sessionMessages = createMemo(() => { + if (useCurrent()) return currentProjection()?.source ?? [] + const id = input.sessionID() + return id ? (sync().data.session_message[id] ?? []) : [] + }) + const parts = (messageID: string) => { + if (useCurrent()) return currentProjection()?.parts.get(messageID) ?? [] + return sync().data.part[messageID] ?? [] + } const ready = createMemo(() => { - if (input.current) return input.current.ready() + if (useCurrent()) return input.current!.ready() const id = input.sessionID() return !id || isTimelineReady(sync().data.message[id], serverSync().session.history.loading(id)) }) @@ -65,12 +86,12 @@ export function createTimelineModel(input: { { equals: same }, ) const more = createMemo(() => { - if (input.current) return input.current.more() + if (useCurrent()) return input.current!.more() const id = input.sessionID() return id ? sync().session.history.more(id) : false }) const loading = createMemo(() => { - if (input.current) return input.current.loading() + if (useCurrent()) return input.current!.loading() const id = input.sessionID() return id ? sync().session.history.loading(id) : false }) @@ -79,8 +100,7 @@ export function createTimelineModel(input: { sessionID: input.sessionID, more, loading, - loadMore: (sessionID) => - input.current ? input.current.loadOlder() : sync().session.history.loadMore(sessionID), + loadMore: (sessionID) => (useCurrent() ? input.current!.loadOlder() : sync().session.history.loadMore(sessionID)), before: options?.before, after: options?.after, }) @@ -92,6 +112,8 @@ export function createTimelineModel(input: { history: { loadOlder, loading, more }, lastUserMessage: createMemo(() => visibleUserMessages().at(-1)), messages, + sessionMessages, + parts, ready, resource, userMessages, diff --git a/packages/app/src/utils/server-compat.test.ts b/packages/app/src/utils/server-compat.test.ts index 52e5ec6e3bee..46e57fa73d93 100644 --- a/packages/app/src/utils/server-compat.test.ts +++ b/packages/app/src/utils/server-compat.test.ts @@ -4,7 +4,10 @@ import { createCompatibleApi } from "./server-compat" function setup( protocol: "v1" | "v2" | Promise<"v1" | "v2">, - responses?: { vcs?: { branch: string; default_branch: string } }, + responses?: { + vcs?: { branch: string; default_branch: string } + mcpResource?: { location: { directory: string; project: { id: string; directory: string } }; data: unknown } + }, ) { const requests: Request[] = [] const fetcher = Object.assign( @@ -37,6 +40,8 @@ function setup( } if (request.method === "GET" && new URL(request.url).pathname === "/vcs") return Response.json(responses?.vcs ?? {}) + if (request.method === "GET" && new URL(request.url).pathname === "/api/mcp/resource") + return Response.json(responses?.mcpResource ?? []) if (request.method === "GET") return Response.json([]) return new Response(undefined, { status: 204 }) }, @@ -147,6 +152,16 @@ describe("createCompatibleApi", () => { expect(detections).toBe(1) }) + test("normalizes the current MCP resource catalog for the legacy app shape", async () => { + const { api } = setup("v2", { + mcpResource: { location: { directory: "/repo", project: { id: "project", directory: "/repo" } }, data: [] }, + }) + + expect(await api.mcp.resource.catalog()).toMatchObject({ + data: { resources: [], templates: [] }, + }) + }) + /* test("keeps V2 session actions on the current API", async () => { const { api, requests } = setup("v2") diff --git a/packages/app/src/utils/server-compat.ts b/packages/app/src/utils/server-compat.ts index 1df1338b71e5..c2dd283e51b9 100644 --- a/packages/app/src/utils/server-compat.ts +++ b/packages/app/src/utils/server-compat.ts @@ -85,12 +85,65 @@ function sessionInfo(session: Session): SessionInfo { export function createCompatibleApi(input: CompatibleInput): CompatibleApi { const v1 = createV1Api(input) + const current = createCurrentApi(input.current) return lazyApi( - input.protocol.then((protocol) => (protocol === "v1" ? v1 : input.current)), - input.current, + input.protocol.then((protocol) => (protocol === "v1" ? v1 : current)), + current, ) } +function createCurrentApi(input: ServerApi): ServerApi { + return { + ...input, + project: { + ...input.project, + async list(...args) { + return unwrapLocated(await input.project.list(...args)) + }, + async current(...args) { + return unwrapLocated(await input.project.current(...args)) + }, + async directories(...args) { + return unwrapLocated(await input.project.directories(...args)) + }, + }, + mcp: { + ...input.mcp, + async list(...args) { + return normalizeMcpList(await input.mcp.list(...args)) + }, + resource: { + ...input.mcp.resource, + async catalog(...args) { + return normalizeMcpResourceCatalog(await input.mcp.resource.catalog(...args)) + }, + }, + }, + } +} + +function unwrapLocated(value: T): T { + if (value !== null && typeof value === "object" && "data" in value) return value.data as T + return value +} + +function normalizeMcpList(value: T): T { + if (Array.isArray(value.data)) return value + if (value.data === null || typeof value.data !== "object") return value + return { + ...value, + data: Object.entries(value.data).map(([name, status]) => ({ name, status })), + } as T +} + +function normalizeMcpResourceCatalog(value: T): T { + if (!Array.isArray(value.data)) return value + return { + ...value, + data: { resources: value.data, templates: [] }, + } as T +} + function lazyApi(implementation: Promise, shape: T): T { const cache = new Map() return new Proxy(shape, { diff --git a/packages/app/src/utils/server-protocol.test.ts b/packages/app/src/utils/server-protocol.test.ts index 9cd309b75a33..0486254b0137 100644 --- a/packages/app/src/utils/server-protocol.test.ts +++ b/packages/app/src/utils/server-protocol.test.ts @@ -8,19 +8,31 @@ const mockFetch = (run: (input: string | URL | Request) => Promise) => Object.assign(run, { preconnect: globalThis.fetch.preconnect }) describe("detectServerProtocol", () => { - test("prefers the legacy health endpoint when both API generations exist", async () => { + test("prefers the current session endpoint when both API generations exist", async () => { const fetcher = mockFetch((input) => { const path = new URL(input instanceof Request ? input.url : input).pathname + if (path === "/api/session") return Promise.resolve(json({ data: [], cursor: {} })) if (path === "/global/health") return Promise.resolve(json({ healthy: true, version: "1.18.4" })) return Promise.resolve(json({ healthy: true, version: "2.0.0", pid: 123 })) }) - expect(await detectServerProtocol(server, fetcher)).toBe("v1") + expect(await detectServerProtocol(server, fetcher)).toBe("v2") + }) + + test("recognizes the current API from its session envelope", async () => { + const fetcher = mockFetch((input) => { + const path = new URL(input instanceof Request ? input.url : input).pathname + if (path === "/api/session") return Promise.resolve(json({ data: [], cursor: { next: undefined } })) + return Promise.resolve(json({}, 404)) + }) + + expect(await detectServerProtocol(server, fetcher)).toBe("v2") }) test("recognizes V2 health by its process identifier", async () => { const fetcher = mockFetch((input) => { const path = new URL(input instanceof Request ? input.url : input).pathname + if (path === "/api/session") return Promise.resolve(json({}, 404)) if (path === "/global/health") return Promise.resolve(json({}, 404)) return Promise.resolve(json({ healthy: true, version: "2.0.0", pid: 123 })) }) @@ -31,6 +43,7 @@ describe("detectServerProtocol", () => { test("recognizes the transitional V1 API health response", async () => { const fetcher = mockFetch((input) => { const path = new URL(input instanceof Request ? input.url : input).pathname + if (path === "/api/session") return Promise.resolve(json({}, 404)) if (path === "/api/health") return Promise.resolve(json({}, 404)) return Promise.resolve(json({ healthy: true })) }) diff --git a/packages/app/src/utils/server-protocol.ts b/packages/app/src/utils/server-protocol.ts index 27b8dc208eac..d86447b8063e 100644 --- a/packages/app/src/utils/server-protocol.ts +++ b/packages/app/src/utils/server-protocol.ts @@ -21,10 +21,19 @@ async function probe(server: ServerConnection.HttpBase, fetch: typeof globalThis return value } +function isCurrentSessionList(value: unknown) { + if (!value || typeof value !== "object") return false + if (!("data" in value) || !Array.isArray(value.data)) return false + return "cursor" in value && !!value.cursor && typeof value.cursor === "object" +} + export async function detectServerProtocol( server: ServerConnection.HttpBase, fetch: typeof globalThis.fetch, ): Promise { + const currentSessions = await probe(server, fetch, "/api/session?limit=1").catch(() => undefined) + if (isCurrentSessionList(currentSessions)) return "v2" + const legacy = await probe(server, fetch, "/global/health").catch(() => undefined) if (legacy && "healthy" in legacy && legacy.healthy === true) return "v1" diff --git a/packages/app/src/utils/session-message.test.ts b/packages/app/src/utils/session-message.test.ts index a69c414e1686..2b0f93ff41ae 100644 --- a/packages/app/src/utils/session-message.test.ts +++ b/packages/app/src/utils/session-message.test.ts @@ -1,8 +1,127 @@ import { describe, expect, test } from "bun:test" import type { SessionMessageInfo } from "@opencode-ai/client/promise" -import { normalizeSessionMessages } from "./session-message" +import { SessionMessage } from "@opencode-ai/schema/session-message" +import { Schema } from "effect" +import { normalizeCurrentSessionMessages, normalizeSessionMessages } from "./session-message" + +const decodeCurrentMessage = Schema.decodeUnknownSync(SessionMessage.Message) describe("normalizeSessionMessages", () => { + test("adapts current messages for the compatibility timeline", () => { + const result = normalizeCurrentSessionMessages("ses_1", [ + decodeCurrentMessage({ + id: "msg_user", + type: "user", + text: "hello", + files: [], + agents: [], + time: { created: 1 }, + }), + decodeCurrentMessage({ + id: "msg_assistant", + type: "assistant", + agent: "build", + model: { id: "model", providerID: "provider" }, + systemPrompt: "prepared system", + toolDefinitions: '[{"name":"read"}]', + content: [ + { type: "text", id: "text", text: "world" }, + { + type: "tool", + id: "call_read", + name: "read", + state: { + status: "completed", + input: { filePath: "README.md" }, + structured: {}, + content: [{ type: "text", text: "contents" }], + }, + time: { created: 2, ran: 3, completed: 4 }, + }, + ], + tokens: { input: 10, output: 2, reasoning: 1, cache: { read: 3, write: 0 } }, + time: { created: 2, completed: 4 }, + }), + ]) + + expect(result.source.map((message) => message.type)).toEqual(["user", "assistant"]) + expect(result.messages).toMatchObject([ + { id: "msg_user", role: "user" }, + { + id: "msg_assistant", + role: "assistant", + parentID: "msg_user", + systemPrompt: "prepared system", + toolDefinitions: '[{"name":"read"}]', + tokens: { input: 10, output: 2, reasoning: 1, cache: { read: 3, write: 0 } }, + }, + ]) + expect(result.parts.get("msg_assistant")).toMatchObject([ + { type: "text", text: "world" }, + { type: "tool", callID: "call_read", state: { status: "completed", output: "contents" } }, + ]) + }) + + test("renders current synthetic text, structured tool metadata, and snapshot diffs", () => { + const result = normalizeCurrentSessionMessages("ses_1", [ + decodeCurrentMessage({ + id: "msg_synthetic", + type: "synthetic", + sessionID: "ses_1", + text: "Generated context", + time: { created: 1 }, + }), + decodeCurrentMessage({ + id: "msg_user", + type: "user", + text: "edit it", + files: [], + agents: [], + time: { created: 2 }, + }), + decodeCurrentMessage({ + id: "msg_assistant", + type: "assistant", + agent: "build", + model: { id: "model", providerID: "provider" }, + snapshot: { diffs: [{ file: "README.md", additions: 2, deletions: 1, status: "modified" }] }, + content: [ + { + type: "tool", + id: "call_edit", + name: "edit", + state: { + status: "completed", + input: { path: "/repo/README.md" }, + structured: { + files: [{ file: "README.md", patch: "@@", additions: 2, deletions: 1 }], + }, + content: [{ type: "text", text: "Edited" }], + }, + time: { created: 3, ran: 4, completed: 5 }, + }, + ], + time: { created: 3, completed: 5 }, + }), + ]) + + expect(result.parts.get("msg_synthetic")).toMatchObject([ + { type: "text", text: "Generated context", synthetic: true }, + ]) + expect(result.parts.get("msg_assistant")).toMatchObject([ + { + type: "tool", + state: { + status: "completed", + metadata: { filediff: { file: "README.md", patch: "@@", additions: 2, deletions: 1 } }, + }, + }, + ]) + expect(result.messages.find((message) => message.id === "msg_user")).toMatchObject({ + summary: { diffs: [{ file: "README.md", additions: 2, deletions: 1, status: "modified" }] }, + }) + }) + test("projects current turns into stable legacy rendering records", () => { const source = [ { id: "msg_1", type: "agent-switched", agent: "build", time: { created: 1 } }, @@ -118,6 +237,33 @@ describe("normalizeSessionMessages", () => { expect(normalizeSessionMessages("ses_1", source).messages).toEqual([]) }) + test("keeps prepared context from compatibility assistant records", () => { + const source = [ + { id: "msg_user", type: "user", text: "hello", time: { created: 1 } }, + { + id: "msg_assistant", + type: "assistant", + agent: "build", + model: { id: "model", providerID: "provider" }, + content: [{ type: "text", text: "world" }], + system_prompt: "prepared system", + tool_defs: '[{"name":"read"}]', + time: { created: 2, completed: 3 }, + } as Extract & { + system_prompt: string + tool_defs: string + }, + ] satisfies SessionMessageInfo[] + + const result = normalizeSessionMessages("ses_1", source) + + expect(result.messages[1]).toMatchObject({ + role: "assistant", + systemPrompt: "prepared system", + toolDefinitions: '[{"name":"read"}]', + }) + }) + test("projects a current shell message into a renderable standalone turn", () => { const source = [ { diff --git a/packages/app/src/utils/session-message.ts b/packages/app/src/utils/session-message.ts index 93d86a66bb25..9027577e23f7 100644 --- a/packages/app/src/utils/session-message.ts +++ b/packages/app/src/utils/session-message.ts @@ -5,12 +5,92 @@ import type { SessionMessageShell, SessionMessageUser, } from "@opencode-ai/client/promise" -import type { AssistantMessage, FilePart, Message, Part, ToolPart, UserMessage } from "@opencode-ai/sdk/v2" +import { SessionMessage } from "@opencode-ai/schema/session-message" +import type { + AssistantMessage, + FilePart, + Message, + Part, + SnapshotFileDiff, + ToolPart, + UserMessage, +} from "@opencode-ai/sdk/v2" import { Option, Schema } from "effect" +type PreparedAssistant = SessionMessageAssistant & { + system_prompt?: string + tool_defs?: string + systemPrompt?: string + toolDefinitions?: string + snapshot?: { diffs?: SnapshotFileDiff[] } +} + const emptyTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } const emptyModel: { id: string; providerID: string; variant?: string } = { id: "", providerID: "" } const decodeToolInput = Schema.decodeUnknownOption(Schema.UnknownFromJsonString) +const encodeCurrentMessage = Schema.encodeSync(SessionMessage.Message) + +type CurrentEncodedMessage = (typeof SessionMessage.Message)["Encoded"] + +/** + * The v2 client and the compatibility renderer use the same persisted messages + * with a small wire-shape difference. Keep the conversion at this boundary so + * the timeline can continue to use its stable legacy projection while current + * events remain the source of truth for live sessions. + */ +export function normalizeCurrentSessionMessages(sessionID: string, source: readonly SessionMessage.Message[]) { + const encoded = source.map((message) => encodeCurrentMessage(message)).map(toLegacyMessage) + return { source: encoded, ...normalizeSessionMessages(sessionID, encoded) } +} + +function toLegacyMessage(message: CurrentEncodedMessage): SessionMessageInfo { + if (message.type === "shell") { + return { + ...message, + shellID: message.callID, + status: message.time.completed === undefined ? "running" : "exited", + output: + message.time.completed === undefined + ? undefined + : { + output: message.output, + cursor: message.output.length, + size: message.output.length, + truncated: false, + }, + } as SessionMessageInfo + } + if (message.type === "synthetic") return { ...message, description: message.text } as SessionMessageInfo + if (message.type !== "assistant") return message as SessionMessageInfo + return { + ...message, + content: message.content.map((content) => { + if (content.type === "text") return { type: "text" as const, text: content.text } + if (content.type === "reasoning") + return { + type: "reasoning" as const, + text: content.text, + state: content.providerMetadata, + time: content.time, + } + return { + type: "tool" as const, + id: content.id, + name: content.name, + executed: content.provider?.executed, + providerState: content.provider?.metadata, + providerResultState: content.provider?.resultMetadata, + state: + content.state.status === "pending" + ? { status: "streaming" as const, input: content.state.input } + : "structured" in content.state + ? { ...content.state, metadata: content.state.structured } + : content.state, + time: content.time, + } + }), + } as SessionMessageInfo +} function record(value: unknown): value is Record { return !!value && typeof value === "object" && !Array.isArray(value) @@ -91,6 +171,8 @@ export function normalizeSessionMessages(sessionID: string, source: readonly Ses modelID: message.model.id, variant: message.model.variant, } + const diffs = (message as PreparedAssistant).snapshot?.diffs + if (diffs) parent.summary = { diffs } } messages.push(assistantMessage(sessionID, parentID, message)) parts.set(message.id, assistantParts(sessionID, message)) @@ -231,6 +313,7 @@ function userParts(sessionID: string, message: SessionMessageUser): Part[] { } function assistantMessage(sessionID: string, parentID: string, message: SessionMessageAssistant): AssistantMessage { + const prepared = message as PreparedAssistant const error = message.error ? message.error.type.toLowerCase().includes("abort") || message.error.type.toLowerCase().includes("interrupt") ? { name: "MessageAbortedError" as const, data: { message: message.error.message } } @@ -251,6 +334,12 @@ function assistantMessage(sessionID: string, parentID: string, message: SessionM path: { cwd: "", root: "" }, cost: message.cost ?? 0, tokens: message.tokens ?? emptyTokens, + ...(prepared.system_prompt === undefined && prepared.systemPrompt === undefined + ? {} + : { systemPrompt: prepared.system_prompt ?? prepared.systemPrompt }), + ...(prepared.tool_defs === undefined && prepared.toolDefinitions === undefined + ? {} + : { toolDefinitions: prepared.tool_defs ?? prepared.toolDefinitions }), finish: message.finish, } } diff --git a/packages/client/src/generated-effect/client.ts b/packages/client/src/generated-effect/client.ts index 461fb35feb6c..a397c5f81c1b 100644 --- a/packages/client/src/generated-effect/client.ts +++ b/packages/client/src/generated-effect/client.ts @@ -424,7 +424,12 @@ type Endpoint5_0Input = { readonly location?: Endpoint5_0Request["query"]["locat const Endpoint5_0 = (raw: RawClient["server.model"]) => (input?: Endpoint5_0Input) => raw["model.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -const adaptGroup5 = (raw: RawClient["server.model"]) => ({ list: Endpoint5_0(raw) }) +type Endpoint5_1Request = Parameters[0] +type Endpoint5_1Input = { readonly location?: Endpoint5_1Request["query"]["location"] } +const Endpoint5_1 = (raw: RawClient["server.model"]) => (input?: Endpoint5_1Input) => + raw["model.default"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) + +const adaptGroup5 = (raw: RawClient["server.model"]) => ({ list: Endpoint5_0(raw), default: Endpoint5_1(raw) }) type Endpoint6_0Request = Parameters[0] type Endpoint6_0Input = { readonly location?: Endpoint6_0Request["query"]["location"] } diff --git a/packages/client/src/generated/client.ts b/packages/client/src/generated/client.ts index 6399590ddb1a..cf4e04a186e6 100644 --- a/packages/client/src/generated/client.ts +++ b/packages/client/src/generated/client.ts @@ -69,6 +69,8 @@ import type { MessagesListOutput, ModelsListInput, ModelsListOutput, + ModelsDefaultInput, + ModelsDefaultOutput, ProvidersListInput, ProvidersListOutput, ProvidersGetInput, @@ -742,6 +744,18 @@ export function make(options: ClientOptions) { }, requestOptions, ), + default: (input?: ModelsDefaultInput, requestOptions?: RequestOptions) => + request( + { + method: "GET", + path: `/api/model/default`, + query: { location: input?.["location"] }, + successStatus: 200, + declaredStatuses: [503, 401, 400], + empty: false, + }, + requestOptions, + ), }, providers: { list: (input?: ProvidersListInput, requestOptions?: RequestOptions) => diff --git a/packages/client/src/generated/types.ts b/packages/client/src/generated/types.ts index c6725880f2dc..3a04f7d2c885 100644 --- a/packages/client/src/generated/types.ts +++ b/packages/client/src/generated/types.ts @@ -5716,6 +5716,65 @@ export type ModelsListOutput = { }> } +export type ModelsDefaultInput = { + readonly location?: { + readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined + }["location"] +} + +export type ModelsDefaultOutput = { + readonly location: { + readonly directory: string + readonly workspaceID?: string + readonly project: { readonly id: string; readonly directory: string } + } + readonly data: { + readonly id: string + readonly providerID: string + readonly family?: string + readonly name: string + readonly api: + | { + readonly id: string + readonly type: "aisdk" + readonly package: string + readonly url?: string + readonly settings?: { readonly [x: string]: JsonValue } + } + | { + readonly id: string + readonly type: "native" + readonly url?: string + readonly settings: { readonly [x: string]: JsonValue } + } + readonly capabilities: { + readonly tools: boolean + readonly input: ReadonlyArray + readonly output: ReadonlyArray + } + readonly request: { + readonly headers: { readonly [x: string]: string } + readonly body: { readonly [x: string]: JsonValue } + readonly variant?: string + } + readonly variants: ReadonlyArray<{ + readonly id: string + readonly headers: { readonly [x: string]: string } + readonly body: { readonly [x: string]: JsonValue } + }> + readonly time: { readonly released: number } + readonly cost: ReadonlyArray<{ + readonly tier?: { readonly type: "context"; readonly size: number } + readonly input: number + readonly output: number + readonly cache: { readonly read: number; readonly write: number } + }> + readonly status: "alpha" | "beta" | "deprecated" | "active" + readonly enabled: boolean + readonly limit: { readonly context: number; readonly input?: number; readonly output: number } + } | null +} + export type ProvidersListInput = { readonly location?: { readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined diff --git a/packages/core/src/agent.ts b/packages/core/src/agent.ts index f71035e581ac..1673227af399 100644 --- a/packages/core/src/agent.ts +++ b/packages/core/src/agent.ts @@ -93,7 +93,7 @@ const layer = Layer.effect( return selectedDefault() }), resolve: Effect.fn("AgentV2.resolve")(function* (id) { - if (id !== undefined) return state.get().agents.get(ID.make(id)) + if (id !== undefined) return state.get().agents.get(ID.make(id)) ?? selectedDefault() return selectedDefault() }), select: Effect.fn("AgentV2.select")(function* (id) { diff --git a/packages/core/src/session/input.ts b/packages/core/src/session/input.ts index 40e7c1d748df..4e7320c70482 100644 --- a/packages/core/src/session/input.ts +++ b/packages/core/src/session/input.ts @@ -404,16 +404,19 @@ export const listQueued = Effect.fn("SessionInput.listQueued")(function* ( .orderBy(asc(SessionInputTable.admitted_seq)) .all() .pipe(Effect.orDie) - return rows.map((row, position) => { - if (row.payload === null) throw new LifecycleConflict({ id: SessionMessage.ID.make(row.id) }) - return Queued.make({ - id: SessionMessage.ID.make(row.id), - sessionID, - position, - timeCreated: DateTime.makeUnsafe(row.time_created), - payload: decodePayload(row.payload), - }) - }) + // Prompt-only queue records predate typed queue payloads. They remain + // drainable by the runner but cannot be edited through the typed queue API. + return rows + .filter((row) => row.payload !== null) + .map((row, position) => + Queued.make({ + id: SessionMessage.ID.make(row.id), + sessionID, + position, + timeCreated: DateTime.makeUnsafe(row.time_created), + payload: decodePayload(row.payload!), + }), + ) }) export const getQueued = Effect.fn("SessionInput.getQueued")(function* ( diff --git a/packages/core/src/session/message-updater.ts b/packages/core/src/session/message-updater.ts index 60d17906fcd9..536f2529f276 100644 --- a/packages/core/src/session/message-updater.ts +++ b/packages/core/src/session/message-updater.ts @@ -69,6 +69,7 @@ export function memory(state: MemoryState): Adapter { }, appendMessage(message) { return Effect.sync(() => { + if (state.messages.some((current) => current.id === message.id)) return state.messages.push(message) }) }, @@ -124,7 +125,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) { }, "session.next.moved": () => Effect.void, "session.next.updated": () => Effect.void, - "session.next.message.imported": () => Effect.void, + "session.next.message.imported": (event) => adapter.appendMessage(event.data.message), "session.next.command.executed": () => Effect.void, "session.next.prompted": (event) => { return adapter.appendMessage( diff --git a/packages/core/test/agent.test.ts b/packages/core/test/agent.test.ts index f4c023e5d5ae..078bcd9229b4 100644 --- a/packages/core/test/agent.test.ts +++ b/packages/core/test/agent.test.ts @@ -99,6 +99,24 @@ describe("AgentV2", () => { }), ) + it.effect("falls back when a persisted session refers to a removed agent", () => + Effect.gen(function* () { + const agent = yield* AgentV2.Service + yield* agent.transform((editor) => + editor.update(AgentV2.defaultID, (info) => { + info.mode = "primary" + info.hidden = false + }), + ) + + expect(yield* agent.select("Sisyphus - ultraworker")).toMatchObject({ + id: "Sisyphus - ultraworker", + info: undefined, + }) + expect(yield* agent.resolve("Sisyphus - ultraworker")).toMatchObject({ id: AgentV2.defaultID }) + }), + ) + it.effect("does not ambiently opt built-in agents into bash", () => Effect.gen(function* () { const agent = yield* AgentV2.Service diff --git a/packages/core/test/session-prompt.test.ts b/packages/core/test/session-prompt.test.ts index dd7b1dd183f0..822ad497c2f2 100644 --- a/packages/core/test/session-prompt.test.ts +++ b/packages/core/test/session-prompt.test.ts @@ -909,6 +909,23 @@ describe("SessionV2.queue", () => { ], }) + it.effect("ignores prompt-only queue records in the typed queue listing", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + + const admitted = yield* session.prompt({ + sessionID, + prompt: Prompt.make({ text: "legacy queue prompt" }), + delivery: "queue", + resume: false, + }) + + expect(admitted.payload).toBeUndefined() + expect(yield* session.queue.list(sessionID)).toEqual([]) + }), + ) + it.effect("persists, revises, expedites, and tombstones queued payloads durably", () => Effect.gen(function* () { yield* setup diff --git a/packages/llm/src/protocols/openai-chat.ts b/packages/llm/src/protocols/openai-chat.ts index b1cc712b88bc..f3ba44cbc0db 100644 --- a/packages/llm/src/protocols/openai-chat.ts +++ b/packages/llm/src/protocols/openai-chat.ts @@ -155,8 +155,12 @@ const OpenAIChatChoice = Schema.Struct({ }) const OpenAIChatEvent = Schema.Struct({ - choices: Schema.Array(OpenAIChatChoice), + choices: optionalArray(OpenAIChatChoice), usage: optionalNull(OpenAIChatUsage), + // OpenCode Zen emits a trailing cost-only event after `[DONE]`. It carries + // no semantic stream data, but accepting the provider metadata keeps tool + // continuations from failing after the tool result has already completed. + cost: optionalNull(Schema.Union([Schema.Number, Schema.String])), }) type OpenAIChatEvent = Schema.Schema.Type type OpenAIChatRequestMessage = LLMRequest["messages"][number] @@ -409,7 +413,7 @@ const step = (state: ParserState, event: OpenAIChatEvent) => Effect.gen(function* () { const events: LLMEvent[] = [] const usage = mapUsage(event.usage) ?? state.usage - const choice = event.choices[0] + const choice = event.choices?.[0] const finishReason = choice?.finish_reason ? mapFinishReason(choice.finish_reason) : state.finishReason const delta = choice?.delta const toolDeltas = delta?.tool_calls ?? [] diff --git a/packages/llm/test/provider/openai-compatible-chat.test.ts b/packages/llm/test/provider/openai-compatible-chat.test.ts index 43ae283e9f7c..eb38f2439222 100644 --- a/packages/llm/test/provider/openai-compatible-chat.test.ts +++ b/packages/llm/test/provider/openai-compatible-chat.test.ts @@ -6,7 +6,7 @@ import { Auth, LLMClient } from "../../src/route" import * as OpenAICompatible from "../../src/providers/openai-compatible" import * as OpenAICompatibleChat from "../../src/protocols/openai-compatible-chat" import { it } from "../lib/effect" -import { dynamicResponse } from "../lib/http" +import { dynamicResponse, fixedResponse } from "../lib/http" import { sseEvents } from "../lib/sse" const Json = Schema.fromJsonString(Schema.Unknown) @@ -40,6 +40,8 @@ const usageChunk = (usage: object) => ({ usage, }) +const costChunk = { cost: "0" } + const providerFamilies = [ ["baseten", OpenAICompatible.baseten, "https://inference.baseten.co/v1"], ["cerebras", OpenAICompatible.cerebras, "https://api.cerebras.ai/v1"], @@ -235,4 +237,27 @@ describe("OpenAI-compatible Chat route", () => { expect(response.events.at(-1)).toMatchObject({ type: "finish", reason: "stop" }) }), ) + + it.effect("accepts a trailing provider cost event after a tool stream", () => + Effect.gen(function* () { + const body = sseEvents( + deltaChunk({ + role: "assistant", + tool_calls: [{ index: 0, id: "call_1", function: { name: "lookup", arguments: '{"query":"weather"}' } }], + }), + deltaChunk({}, "tool_calls"), + usageChunk({ prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 }), + costChunk, + ) + + const response = yield* LLMClient.generate( + LLM.updateRequest(request, { + tools: [{ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } }], + }), + ).pipe(Effect.provide(fixedResponse(body))) + + expect(response.events.at(-1)).toMatchObject({ type: "finish", reason: "tool-calls" }) + expect(response.usage).toMatchObject({ inputTokens: 5, outputTokens: 2, totalTokens: 7 }) + }), + ) }) diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/project.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/project.ts index 3ce34442dd10..5716d49eb594 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/project.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/project.ts @@ -1,5 +1,7 @@ import { Project } from "@/project/project" import { ProjectV2 } from "@opencode-ai/core/project" +import { Location } from "@opencode-ai/schema/location" +import { LocationMiddleware } from "@opencode-ai/server/location" import { Schema } from "effect" import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" import { ProjectNotFoundError } from "../errors" @@ -29,6 +31,16 @@ export const ProjectApi = HttpApi.make("project") description: "Get a list of projects that have been opened with OpenCode.", }), ), + HttpApiEndpoint.get("listV2", "/api/project", { + query: WorkspaceRoutingQuery, + success: described(Location.response(Schema.Array(Project.Info)), "List of projects"), + }).annotateMerge( + OpenApi.annotations({ + identifier: "v2.project.list", + summary: "List all projects", + description: "Get a list of projects that have been opened with OpenCode.", + }), + ), HttpApiEndpoint.get("current", `${root}/current`, { query: WorkspaceRoutingQuery, success: described(Project.Info, "Current project information"), @@ -39,6 +51,16 @@ export const ProjectApi = HttpApi.make("project") description: "Retrieve the currently active project that OpenCode is working with.", }), ), + HttpApiEndpoint.get("currentV2", "/api/project/current", { + query: WorkspaceRoutingQuery, + success: described(Location.response(Project.Info), "Current project information"), + }).annotateMerge( + OpenApi.annotations({ + identifier: "v2.project.current", + summary: "Get current project", + description: "Retrieve the currently active project that OpenCode is working with.", + }), + ), HttpApiEndpoint.post("initGit", `${root}/git/init`, { query: WorkspaceRoutingQuery, success: described(Project.Info, "Project information after git initialization"), @@ -73,6 +95,17 @@ export const ProjectApi = HttpApi.make("project") description: "List known local absolute directories for a project.", }), ), + HttpApiEndpoint.get("directoriesV2", "/api/project/:projectID/directories", { + params: { projectID: ProjectV2.ID }, + query: WorkspaceRoutingQuery, + success: described(Location.response(ProjectV2.Directories), "Project directories"), + }).annotateMerge( + OpenApi.annotations({ + identifier: "v2.project.directories", + summary: "List project directories", + description: "List known local absolute directories for a project.", + }), + ), ) .annotateMerge( OpenApi.annotations({ @@ -82,6 +115,7 @@ export const ProjectApi = HttpApi.make("project") ) .middleware(InstanceContextMiddleware) .middleware(WorkspaceRoutingMiddleware) + .middleware(LocationMiddleware) .middleware(Authorization), ) .annotateMerge( diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/project.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/project.ts index 3c5351aee2ab..8b24370932af 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/project.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/project.ts @@ -1,5 +1,7 @@ import * as InstanceState from "@/effect/instance-state" +import { Location } from "@opencode-ai/core/location" import { Project } from "@/project/project" +import { response } from "@opencode-ai/server/location" import { ProjectV2 } from "@opencode-ai/core/project" import { Effect } from "effect" import { HttpApiBuilder } from "effect/unstable/httpapi" @@ -16,10 +18,21 @@ export const projectHandlers = HttpApiBuilder.group(InstanceHttpApi, "project", return yield* svc.list() }) + const listV2 = Effect.fn("ProjectHttpApi.listV2")(function* () { + return yield* response(svc.list()) + }) + const current = Effect.fn("ProjectHttpApi.current")(function* () { return (yield* InstanceState.context).project }) + const currentV2 = Effect.fn("ProjectHttpApi.currentV2")(function* () { + const location = yield* Location.Service + const current = yield* svc.get(location.project.id) + if (current) return yield* response(Effect.succeed(current)) + return yield* response(Effect.map(InstanceState.context, (ctx) => ctx.project)) + }) + const initGit = Effect.fn("ProjectHttpApi.initGit")(function* () { const ctx = yield* InstanceState.context const next = yield* svc.initGit({ directory: ctx.directory, project: ctx.project }) @@ -53,11 +66,18 @@ export const projectHandlers = HttpApiBuilder.group(InstanceHttpApi, "project", project.directories({ projectID: ctx.params.projectID }), ) + const directoriesV2 = Effect.fn("ProjectHttpApi.directoriesV2")((ctx: { params: { projectID: ProjectV2.ID } }) => + response(project.directories({ projectID: ctx.params.projectID })), + ) + return handlers .handle("list", list) + .handle("listV2", listV2) .handle("current", current) + .handle("currentV2", currentV2) .handle("initGit", initGit) .handle("update", update) .handle("directories", directories) + .handle("directoriesV2", directoriesV2) }), ) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index 610b0221fae9..a6986aac6c94 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -8,6 +8,7 @@ import { SessionShare } from "@/share/session" import { Session } from "@/session/session" import { SessionCompaction } from "@/session/compaction" import { MessageV2 } from "@/session/message-v2" +import { firstPreparedContextID, projectPreparedContext } from "@/session/prepared-context" import { SessionPrompt } from "@/session/prompt" import { SessionRevert } from "@/session/revert" import { SessionRunState } from "@/session/run-state" @@ -118,7 +119,9 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", } yield* requireSession(ctx.params.sessionID) if (ctx.query.limit === undefined || ctx.query.limit === 0) { - return yield* SessionError.mapStorageNotFound(session.messages({ sessionID: ctx.params.sessionID })) + return projectPreparedContext( + yield* SessionError.mapStorageNotFound(session.messages({ sessionID: ctx.params.sessionID })), + ) } const page = yield* SessionError.mapStorageNotFound( @@ -128,7 +131,14 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", before: ctx.query.before, }), ) - if (!page.cursor) return page.items + if (!page.cursor) return projectPreparedContext(page.items) + const pagePreparedID = firstPreparedContextID(page.items) + const firstPreparedID = + pagePreparedID === undefined + ? undefined + : firstPreparedContextID( + yield* SessionError.mapStorageNotFound(session.messages({ sessionID: ctx.params.sessionID })), + ) const request = yield* HttpServerRequest.HttpServerRequest // toURL() honors the Host + x-forwarded-proto headers, so the Link @@ -136,7 +146,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", const url = Option.getOrElse(HttpServerRequest.toURL(request), () => 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, { + return HttpServerResponse.jsonUnsafe(projectPreparedContext(page.items, firstPreparedID), { headers: { "Access-Control-Expose-Headers": "Link, X-Next-Cursor", Link: `<${url.toString()}>; rel="next"`, diff --git a/packages/opencode/src/session/prepared-context.ts b/packages/opencode/src/session/prepared-context.ts new file mode 100644 index 000000000000..1d8d7049bdb9 --- /dev/null +++ b/packages/opencode/src/session/prepared-context.ts @@ -0,0 +1,22 @@ +import type { SessionV1 } from "@opencode-ai/core/v1/session" + +export function firstPreparedContextID(messages: readonly SessionV1.WithParts[]) { + return messages.find( + (message) => + message.info.role === "assistant" && + (message.info.system_prompt !== undefined || message.info.tool_defs !== undefined), + )?.info.id +} + +export function projectPreparedContext( + messages: readonly SessionV1.WithParts[], + firstPreparedID = firstPreparedContextID(messages), +) { + if (firstPreparedID === undefined) return messages + + return messages.map((message) => { + if (message.info.role !== "assistant" || message.info.id === firstPreparedID) return message + const { system_prompt: _, tool_defs: __, ...info } = message.info + return { ...message, info } + }) +} diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 773e9521323d..3d6333fa3234 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -2,7 +2,7 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { PermissionV1 } from "@opencode-ai/core/v1/permission" import { Image } from "@/image/image" import { SessionV1 } from "@opencode-ai/core/v1/session" -import { Cause, Deferred, Effect, Exit, Layer, Context, Scope, Schema } from "effect" +import { Cause, Deferred, Effect, Exit, Layer, Context, Option, Scope, Schema } from "effect" import * as Stream from "effect/Stream" import { Agent } from "@/agent/agent" import { Config } from "@/config/config" @@ -120,6 +120,18 @@ const layer = Layer.effect( aborted, }) + const preparedContextStored = Option.isSome( + yield* session + .findMessage( + input.sessionID, + (message) => + message.info.role === "assistant" && + message.info.id !== input.assistantMessage.id && + (message.info.system_prompt !== undefined || message.info.tool_defs !== undefined), + ) + .pipe(Effect.orDie), + ) + const settleToolCall = Effect.fn("SessionProcessor.settleToolCall")(function* (toolCallID: string) { const done = ctx.toolcalls[toolCallID]?.done delete ctx.toolcalls[toolCallID] @@ -640,6 +652,7 @@ const layer = Layer.effect( const stream = llm.stream(streamInput, { onPrepared: (context) => Effect.gen(function* () { + if (preparedContextStored) return ctx.assistantMessage.system_prompt = context.systemPrompt ctx.assistantMessage.tool_defs = context.toolDefs yield* session.updateMessage(ctx.assistantMessage) diff --git a/packages/opencode/test/server/httpapi-exercise/index.ts b/packages/opencode/test/server/httpapi-exercise/index.ts index c29e093453e6..6516a5d07c27 100644 --- a/packages/opencode/test/server/httpapi-exercise/index.ts +++ b/packages/opencode/test/server/httpapi-exercise/index.ts @@ -176,6 +176,7 @@ const scenarios: Scenario[] = [ .status(400), http.protected.get("/config/providers", "config.providers").json(), http.protected.get("/project", "project.list").json(200, array, "status"), + http.protected.get("/api/project", "v2.project.list").json(200, array, "status"), http.protected.get("/project/current", "project.current").json( 200, (body, ctx) => { @@ -184,6 +185,14 @@ const scenarios: Scenario[] = [ }, "status", ), + http.protected.get("/api/project/current", "v2.project.current").json( + 200, + (body, ctx) => { + object(body) + check(body.worktree === ctx.directory, "current project should resolve from scenario directory") + }, + "status", + ), http.protected .patch("/project/{projectID}", "project.update") .mutating() @@ -235,6 +244,14 @@ const scenarios: Scenario[] = [ headers: ctx.headers(), })) .json(200, array, "status"), + http.protected + .get("/api/project/{projectID}/directories", "v2.project.directories") + .seeded((ctx) => ctx.project()) + .at((ctx) => ({ + path: route("/api/project/{projectID}/directories", { projectID: ctx.state.id }), + headers: ctx.headers(), + })) + .json(200, array, "status"), http.protected .post("/experimental/project/{projectID}/copy/generate-name", "experimental.projectCopy.generateName") .seeded((ctx) => ctx.project()) @@ -678,6 +695,12 @@ const scenarios: Scenario[] = [ http.protected.get("/api/location", "v2.location.get").json(200, object), http.protected.get("/api/agent", "v2.agent.list").json(200, locationData(array)), http.protected.get("/api/model", "v2.model.list").json(200, locationData(array)), + http.protected.get("/api/model/default", "v2.model.default").json( + 200, + locationData((value) => { + if (value !== null) object(value) + }), + ), http.protected.get("/api/provider", "v2.provider.list").json(200, locationData(array)), http.protected.get("/api/mcp", "v2.mcp.status").json(200, locationData(object)), http.protected @@ -727,9 +750,7 @@ const scenarios: Scenario[] = [ })) .json(400, object, "status"), http.protected.get("/api/mcp/resource", "v2.mcp.resources").json(200, locationData(array)), - http.protected - .get("/api/mcp/resource-template", "v2.mcp.resourceTemplates") - .json(200, locationData(array)), + http.protected.get("/api/mcp/resource-template", "v2.mcp.resourceTemplates").json(200, locationData(array)), http.protected .post("/api/mcp/resource/read", "v2.mcp.resourceRead") .at((ctx) => ({ diff --git a/packages/opencode/test/server/session-messages.test.ts b/packages/opencode/test/server/session-messages.test.ts index c47913bde5ab..f75b700cfc24 100644 --- a/packages/opencode/test/server/session-messages.test.ts +++ b/packages/opencode/test/server/session-messages.test.ts @@ -156,6 +156,7 @@ describe("session messages endpoint", () => { }), ), { git: true }, + { timeout: 15_000 }, ) it.instance( diff --git a/packages/opencode/test/session/prepared-context.test.ts b/packages/opencode/test/session/prepared-context.test.ts new file mode 100644 index 000000000000..16c5a9d0e047 --- /dev/null +++ b/packages/opencode/test/session/prepared-context.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from "bun:test" +import { projectPreparedContext } from "../../src/session/prepared-context" +import type { SessionV1 } from "@opencode-ai/core/v1/session" + +type Assistant = Extract + +const assistant = (id: string, prepared = false): SessionV1.WithParts => ({ + info: { + id: id as Assistant["id"], + sessionID: "ses_test" as Assistant["sessionID"], + role: "assistant", + time: { created: 1 }, + parentID: "msg_parent" as SessionV1.MessageID, + modelID: "model" as Assistant["modelID"], + providerID: "provider" as Assistant["providerID"], + mode: "build", + agent: "build", + path: { cwd: "/tmp", root: "/tmp" }, + cost: 0, + tokens: { input: 1, output: 1, reasoning: 0, cache: { read: 0, write: 0 } }, + ...(prepared ? { system_prompt: "system", tool_defs: "tools" } : {}), + }, + parts: [], +}) + +describe("projectPreparedContext", () => { + test("keeps prepared metadata only on the first assistant", () => { + const messages = [assistant("msg_1", true), assistant("msg_2", true)] + const projected = projectPreparedContext(messages) + + expect(projected[0]?.info).toMatchObject({ system_prompt: "system", tool_defs: "tools" }) + expect(projected[1]?.info).not.toHaveProperty("system_prompt") + expect(projected[1]?.info).not.toHaveProperty("tool_defs") + expect(messages[1]?.info).toHaveProperty("system_prompt") + }) +}) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 414678faca8c..ac56b23ea8b9 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -593,6 +593,15 @@ it.instance("loop persists prepared system prompt and tool definitions for conte expect(stored.info.system_prompt).toBe(providerSystemPrompt(providerInput)) expect(storedToolNames(stored.info.tool_defs)).toEqual(providerToolNames(providerInput)) expect(storedToolNames(stored.info.tool_defs).length).toBeGreaterThan(0) + + yield* user(chat.id, "second turn") + yield* llm.text("second response") + const second = yield* prompt.loop({ sessionID: chat.id }) + const storedSecond = yield* MessageV2.get({ sessionID: chat.id, messageID: second.info.id }) + expect(storedSecond.info.role).toBe("assistant") + if (storedSecond.info.role !== "assistant") return + expect(storedSecond.info.system_prompt).toBeUndefined() + expect(storedSecond.info.tool_defs).toBeUndefined() }), ) diff --git a/packages/protocol/src/groups/model.ts b/packages/protocol/src/groups/model.ts index 9125f9528929..569ad862a648 100644 --- a/packages/protocol/src/groups/model.ts +++ b/packages/protocol/src/groups/model.ts @@ -21,6 +21,21 @@ export const ModelGroup = HttpApiGroup.make("server.model") }), ), ) + .add( + HttpApiEndpoint.get("model.default", "/api/model/default", { + query: LocationQuery, + success: Location.response(Schema.NullOr(Model.Info)), + error: ServiceUnavailableError, + }) + .annotateMerge(locationQueryOpenApi) + .annotateMerge( + OpenApi.annotations({ + identifier: "v2.model.default", + summary: "Get the default model", + description: "Retrieve the configured default model, or the newest available model when none is configured.", + }), + ), + ) .annotateMerge( OpenApi.annotations({ title: "models", diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 6a57300c8b18..98cb51b61f8f 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -318,6 +318,8 @@ import type { V2McpResourceTemplatesResponses, V2McpStatusErrors, V2McpStatusResponses, + V2ModelDefaultErrors, + V2ModelDefaultResponses, V2ModelListErrors, V2ModelListResponses, V2PermissionRequestListErrors, @@ -332,6 +334,12 @@ import type { V2ProjectCopyRefreshResponses, V2ProjectCopyRemoveErrors, V2ProjectCopyRemoveResponses, + V2ProjectCurrentErrors, + V2ProjectCurrentResponses, + V2ProjectDirectoriesErrors, + V2ProjectDirectoriesResponses, + V2ProjectListErrors, + V2ProjectListResponses, V2ProviderGetErrors, V2ProviderGetResponses, V2ProviderListErrors, @@ -2743,13 +2751,13 @@ export class Project extends HeyApiClient { } } -export class Pty extends HeyApiClient { +export class Project2 extends HeyApiClient { /** - * List available shells + * List all projects * - * Get a list of available shells on the system. + * Get a list of projects that have been opened with OpenCode. */ - public shells( + public list( parameters?: { directory?: string workspace?: string @@ -2767,19 +2775,19 @@ export class Pty extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ - url: "/pty/shells", + return (options?.client ?? this.client).get({ + url: "/api/project", ...options, ...params, }) } /** - * List PTY sessions + * Get current project * - * Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode. + * Retrieve the currently active project that OpenCode is working with. */ - public list( + public current( parameters?: { directory?: string workspace?: string @@ -2797,29 +2805,23 @@ export class Pty extends HeyApiClient { }, ], ) - return (options?.client ?? this.client).get({ - url: "/pty", + return (options?.client ?? this.client).get({ + url: "/api/project/current", ...options, ...params, }) } /** - * Create PTY session + * List project directories * - * Create a new pseudo-terminal (PTY) session for running shell commands and processes. + * List known local absolute directories for a project. */ - public create( - parameters?: { + public directories( + parameters: { + projectID: string directory?: string workspace?: string - command?: string - args?: Array - cwd?: string - title?: string - env?: { - [key: string]: string - } }, options?: Options, ) { @@ -2828,108 +2830,160 @@ export class Pty extends HeyApiClient { [ { args: [ + { in: "path", key: "projectID" }, { in: "query", key: "directory" }, { in: "query", key: "workspace" }, - { in: "body", key: "command" }, - { in: "body", key: "args" }, - { in: "body", key: "cwd" }, - { in: "body", key: "title" }, - { in: "body", key: "env" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/pty", + return (options?.client ?? this.client).get< + V2ProjectDirectoriesResponses, + V2ProjectDirectoriesErrors, + ThrowOnError + >({ + url: "/api/project/{projectID}/directories", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } +} +export class Health extends HeyApiClient { /** - * Remove PTY session + * Check server health * - * Remove and terminate a specific pseudo-terminal (PTY) session. + * Check whether the API server is ready to accept requests. */ - public remove( - parameters: { - ptyID: string - directory?: string - workspace?: string + public get(options?: Options) { + return (options?.client ?? this.client).get({ + url: "/api/health", + ...options, + }) + } +} + +export class Location extends HeyApiClient { + /** + * Get location + * + * Resolve the requested location or the server default location. + */ + public get( + parameters?: { + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "ptyID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).delete({ - url: "/pty/{ptyID}", + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/location", ...options, ...params, }) } +} +export class Agent extends HeyApiClient { /** - * Get PTY session + * List agents * - * Retrieve detailed information about a specific pseudo-terminal (PTY) session. + * Retrieve currently registered agents. */ - public get( + public list( + parameters?: { + location?: { + directory?: string + workspace?: string + } + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/agent", + ...options, + ...params, + }) + } +} + +export class Drain extends HeyApiClient { + /** + * Pause queue drain + */ + public pause( parameters: { - ptyID: string - directory?: string - workspace?: string + sessionID: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "ptyID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "/pty/{ptyID}", + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).post< + V2SessionQueueDrainPauseResponses, + V2SessionQueueDrainPauseErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/queue/drain-pause", ...options, ...params, }) } /** - * Update PTY session + * Resume queue drain + */ + public resume( + parameters: { + sessionID: string + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).post< + V2SessionQueueDrainResumeResponses, + V2SessionQueueDrainResumeErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/queue/drain-resume", + ...options, + ...params, + }) + } +} + +export class Queue extends HeyApiClient { + /** + * List queued inputs + */ + public list( + parameters: { + sessionID: string + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/session/{sessionID}/queue", + ...options, + ...params, + }) + } + + /** + * Enqueue input * - * Update properties of an existing pseudo-terminal (PTY) session. + * Durably enqueue an input without starting an idle drain unless resume is true. */ - public update( + public enqueue( parameters: { - ptyID: string - directory?: string - workspace?: string - title?: string - size?: { - rows: number - cols: number - } + sessionID: string + id?: string + payload?: SessionInputPayload + resume?: boolean }, options?: Options, ) { @@ -2938,17 +2992,20 @@ export class Pty extends HeyApiClient { [ { args: [ - { in: "path", key: "ptyID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "title" }, - { in: "body", key: "size" }, + { in: "path", key: "sessionID" }, + { in: "body", key: "id" }, + { in: "body", key: "payload" }, + { in: "body", key: "resume" }, ], }, ], ) - return (options?.client ?? this.client).put({ - url: "/pty/{ptyID}", + return (options?.client ?? this.client).post< + V2SessionQueueEnqueueResponses, + V2SessionQueueEnqueueErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/queue", ...options, ...params, headers: { @@ -2960,15 +3017,12 @@ export class Pty extends HeyApiClient { } /** - * Create PTY WebSocket token - * - * Create a short-lived ticket for opening a PTY WebSocket connection. + * Remove queued input */ - public connectToken( + public remove( parameters: { - ptyID: string - directory?: string - workspace?: string + sessionID: string + messageID: string }, options?: Options, ) { @@ -2977,32 +3031,30 @@ export class Pty extends HeyApiClient { [ { args: [ - { in: "path", key: "ptyID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "path", key: "sessionID" }, + { in: "path", key: "messageID" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/pty/{ptyID}/connect-token", + return (options?.client ?? this.client).delete< + V2SessionQueueRemoveResponses, + V2SessionQueueRemoveErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/queue/{messageID}", ...options, ...params, }) } /** - * Connect to PTY session - * - * Establish a WebSocket connection to interact with a pseudo-terminal (PTY) session in real-time. + * Get queued input */ - public connect( + public get( parameters: { - ptyID: string - directory?: string - workspace?: string - cursor?: string - ticket?: string + sessionID: string + messageID: string }, options?: Options, ) { @@ -3011,33 +3063,27 @@ export class Pty extends HeyApiClient { [ { args: [ - { in: "path", key: "ptyID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "query", key: "cursor" }, - { in: "query", key: "ticket" }, + { in: "path", key: "sessionID" }, + { in: "path", key: "messageID" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/pty/{ptyID}/connect", + return (options?.client ?? this.client).get({ + url: "/api/session/{sessionID}/queue/{messageID}", ...options, ...params, }) } -} -export class Question extends HeyApiClient { /** - * List pending questions - * - * Get all pending question requests across all sessions. + * Update queued input */ - public list( - parameters?: { - directory?: string - workspace?: string + public update( + parameters: { + sessionID: string + messageID: string + payload?: SessionInputPayload }, options?: Options, ) { @@ -3046,30 +3092,39 @@ export class Question extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "path", key: "sessionID" }, + { in: "path", key: "messageID" }, + { in: "body", key: "payload" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/question", + return (options?.client ?? this.client).patch< + V2SessionQueueUpdateResponses, + V2SessionQueueUpdateErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/queue/{messageID}", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Reply to question request + * Send queued input now * - * Provide answers to a question request from the AI assistant. + * Atomically expedite a queued input into steer delivery and wake execution. */ - public reply( + public send( parameters: { - requestID: string - directory?: string - workspace?: string - answers?: Array + sessionID: string + messageID: string + payload?: SessionInputPayload }, options?: Options, ) { @@ -3078,16 +3133,15 @@ export class Question extends HeyApiClient { [ { args: [ - { in: "path", key: "requestID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "answers" }, + { in: "path", key: "sessionID" }, + { in: "path", key: "messageID" }, + { in: "body", key: "payload" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/question/{requestID}/reply", + return (options?.client ?? this.client).post({ + url: "/api/session/{sessionID}/queue/{messageID}/send", ...options, ...params, headers: { @@ -3098,16 +3152,23 @@ export class Question extends HeyApiClient { }) } + private _drain?: Drain + get drain(): Drain { + return (this._drain ??= new Drain({ client: this.client })) + } +} + +export class Revert extends HeyApiClient { /** - * Reject question request + * Stage session revert * - * Reject a question request from the AI assistant. + * Stage or move a reversible session boundary and optionally apply its file changes. */ - public reject( + public stage( parameters: { - requestID: string - directory?: string - workspace?: string + sessionID: string + messageID?: string + files?: boolean }, options?: Options, ) { @@ -3116,15 +3177,66 @@ export class Question extends HeyApiClient { [ { args: [ - { in: "path", key: "requestID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "path", key: "sessionID" }, + { in: "body", key: "messageID" }, + { in: "body", key: "files" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/question/{requestID}/reject", + return (options?.client ?? this.client).post< + V2SessionRevertStageResponses, + V2SessionRevertStageErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/revert/stage", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Clear staged revert + */ + public clear( + parameters: { + sessionID: string + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).post< + V2SessionRevertClearResponses, + V2SessionRevertClearErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/revert/clear", + ...options, + ...params, + }) + } + + /** + * Commit staged revert + */ + public commit( + parameters: { + sessionID: string + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).post< + V2SessionRevertCommitResponses, + V2SessionRevertCommitErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/revert/commit", ...options, ...params, }) @@ -3133,47 +3245,45 @@ export class Question extends HeyApiClient { export class Permission extends HeyApiClient { /** - * List pending permissions + * List session permission requests * - * Get all pending permission requests across all sessions. + * Retrieve pending permission requests owned by a session. */ public list( - parameters?: { - directory?: string - workspace?: string + parameters: { + sessionID: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "/permission", + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).get< + V2SessionPermissionListResponses, + V2SessionPermissionListErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/permission", ...options, ...params, }) } /** - * Respond to permission request + * Create permission request * - * Approve or deny a permission request from the AI assistant. + * Evaluate and, when approval is required, create a permission request for a session. */ - public reply( + public create( parameters: { - requestID: string - directory?: string - workspace?: string - reply?: "once" | "always" | "reject" - message?: string + sessionID: string + id?: string + action?: string + resources?: Array + save?: Array + metadata?: { + [key: string]: unknown + } + source?: PermissionV2Source + agent?: string }, options?: Options, ) { @@ -3182,17 +3292,24 @@ export class Permission extends HeyApiClient { [ { args: [ - { in: "path", key: "requestID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "reply" }, - { in: "body", key: "message" }, + { in: "path", key: "sessionID" }, + { in: "body", key: "id" }, + { in: "body", key: "action" }, + { in: "body", key: "resources" }, + { in: "body", key: "save" }, + { in: "body", key: "metadata" }, + { in: "body", key: "source" }, + { in: "body", key: "agent" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/permission/{requestID}/reply", + return (options?.client ?? this.client).post< + V2SessionPermissionCreateResponses, + V2SessionPermissionCreateErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/permission", ...options, ...params, headers: { @@ -3204,19 +3321,14 @@ export class Permission extends HeyApiClient { } /** - * Respond to permission - * - * Approve or deny a permission request from the AI assistant. + * Get permission request * - * @deprecated + * Retrieve a pending permission request owned by a session. */ - public respond( + public get( parameters: { sessionID: string - permissionID: string - directory?: string - workspace?: string - response?: "once" | "always" | "reject" + requestID: string }, options?: Options, ) { @@ -3226,42 +3338,33 @@ export class Permission extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "path", key: "permissionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "response" }, + { in: "path", key: "requestID" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/permissions/{permissionID}", + return (options?.client ?? this.client).get< + V2SessionPermissionGetResponses, + V2SessionPermissionGetErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/permission/{requestID}", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } -} -export class Oauth extends HeyApiClient { /** - * Start OAuth authorization + * Reply to pending permission request * - * Start the OAuth authorization flow for a provider. + * Respond to a pending permission request owned by a session. */ - public authorize( + public reply( parameters: { - providerID: string - directory?: string - workspace?: string - method?: number - inputs?: { - [key: string]: string - } + sessionID: string + requestID: string + reply?: PermissionV2Reply + message?: string }, options?: Options, ) { @@ -3270,21 +3373,20 @@ export class Oauth extends HeyApiClient { [ { args: [ - { in: "path", key: "providerID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "method" }, - { in: "body", key: "inputs" }, + { in: "path", key: "sessionID" }, + { in: "path", key: "requestID" }, + { in: "body", key: "reply" }, + { in: "body", key: "message" }, ], }, ], ) return (options?.client ?? this.client).post< - ProviderOauthAuthorizeResponses, - ProviderOauthAuthorizeErrors, + V2SessionPermissionReplyResponses, + V2SessionPermissionReplyErrors, ThrowOnError >({ - url: "/provider/{providerID}/oauth/authorize", + url: "/api/session/{sessionID}/permission/{requestID}/reply", ...options, ...params, headers: { @@ -3294,63 +3396,42 @@ export class Oauth extends HeyApiClient { }, }) } +} +export class Question extends HeyApiClient { /** - * Handle OAuth callback + * List session question requests * - * Handle the OAuth callback from a provider after user authorization. + * Retrieve pending question requests owned by a session. */ - public callback( + public list( parameters: { - providerID: string - directory?: string - workspace?: string - method?: number - code?: string + sessionID: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "providerID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "method" }, - { in: "body", key: "code" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post< - ProviderOauthCallbackResponses, - ProviderOauthCallbackErrors, + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).get< + V2SessionQuestionListResponses, + V2SessionQuestionListErrors, ThrowOnError >({ - url: "/provider/{providerID}/oauth/callback", + url: "/api/session/{sessionID}/question", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } -} -export class Provider extends HeyApiClient { /** - * List providers + * Reply to pending question request * - * Get a list of all available AI providers, including both available and connected ones. + * Answer a pending question request owned by a session. */ - public list( - parameters?: { - directory?: string - workspace?: string + public reply( + parameters: { + sessionID: string + requestID: string + questionV2Reply: QuestionV2Reply }, options?: Options, ) { @@ -3359,28 +3440,38 @@ export class Provider extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "path", key: "sessionID" }, + { in: "path", key: "requestID" }, + { key: "questionV2Reply", map: "body" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/provider", + return (options?.client ?? this.client).post< + V2SessionQuestionReplyResponses, + V2SessionQuestionReplyErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/question/{requestID}/reply", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Get provider auth methods + * Reject pending question request * - * Retrieve available authentication methods for all AI providers. + * Reject a pending question request owned by a session. */ - public auth( - parameters?: { - directory?: string - workspace?: string + public reject( + parameters: { + sessionID: string + requestID: string }, options?: Options, ) { @@ -3389,41 +3480,40 @@ export class Provider extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "path", key: "sessionID" }, + { in: "path", key: "requestID" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/provider/auth", + return (options?.client ?? this.client).post< + V2SessionQuestionRejectResponses, + V2SessionQuestionRejectErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/question/{requestID}/reject", ...options, ...params, }) } - - private _oauth?: Oauth - get oauth(): Oauth { - return (this._oauth ??= new Oauth({ client: this.client })) - } } export class Session2 extends HeyApiClient { /** * List sessions * - * Get a list of all OpenCode sessions, sorted by most recently updated. + * Retrieve sessions in the requested order. Items keep that order across pages; use cursor.next or cursor.previous to move through the ordered list. */ public list( parameters?: { - directory?: string workspace?: string - scope?: "project" - path?: string - roots?: boolean | "true" | "false" - start?: number - search?: string limit?: number + order?: "asc" | "desc" + search?: string + directory?: string + project?: string + subpath?: string + cursor?: string }, options?: Options, ) { @@ -3432,20 +3522,20 @@ export class Session2 extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, { in: "query", key: "workspace" }, - { in: "query", key: "scope" }, - { in: "query", key: "path" }, - { in: "query", key: "roots" }, - { in: "query", key: "start" }, - { in: "query", key: "search" }, { in: "query", key: "limit" }, + { in: "query", key: "order" }, + { in: "query", key: "search" }, + { in: "query", key: "directory" }, + { in: "query", key: "project" }, + { in: "query", key: "subpath" }, + { in: "query", key: "cursor" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/session", + return (options?.client ?? this.client).get({ + url: "/api/session", ...options, ...params, }) @@ -3454,25 +3544,14 @@ export class Session2 extends HeyApiClient { /** * Create session * - * Create a new OpenCode session for interacting with AI assistants and managing conversations. + * Create a session at the requested location. */ public create( parameters?: { - directory?: string - workspace?: string - parentID?: string - title?: string + id?: string agent?: string - model?: { - id: string - providerID: string - variant?: string - } - metadata?: { - [key: string]: unknown - } - permission?: PermissionRuleset - workspaceID?: string + model?: ModelRef + location?: LocationRef }, options?: Options, ) { @@ -3481,21 +3560,16 @@ export class Session2 extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "parentID" }, - { in: "body", key: "title" }, + { in: "body", key: "id" }, { in: "body", key: "agent" }, { in: "body", key: "model" }, - { in: "body", key: "metadata" }, - { in: "body", key: "permission" }, - { in: "body", key: "workspaceID" }, + { in: "body", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/session", + return (options?.client ?? this.client).post({ + url: "/api/session", ...options, ...params, headers: { @@ -3507,45 +3581,43 @@ export class Session2 extends HeyApiClient { } /** - * Get session status + * List active sessions * - * Retrieve the current status of all sessions, including active, idle, and completed states. + * Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive. */ - public status( - parameters?: { - directory?: string - workspace?: string + public active(options?: Options) { + return (options?.client ?? this.client).get({ + url: "/api/session/active", + ...options, + }) + } + + /** + * Get session + * + * Retrieve a session by ID. + */ + public get( + parameters: { + sessionID: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "/session/status", + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/session/{sessionID}", ...options, ...params, }) } /** - * Delete session - * - * Delete a session and permanently remove all associated data, including messages and history. + * Update session */ - public delete( + public update( parameters: { sessionID: string - directory?: string - workspace?: string + title?: string }, options?: Options, ) { @@ -3555,29 +3627,30 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "body", key: "title" }, ], }, ], ) - return (options?.client ?? this.client).delete({ - url: "/session/{sessionID}", - ...options, + return (options?.client ?? this.client).patch({ + url: "/api/session/{sessionID}", + ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Get session - * - * Retrieve detailed information about a specific OpenCode session. + * Fork session */ - public get( + public fork( parameters: { sessionID: string - directory?: string - workspace?: string + messageID?: string }, options?: Options, ) { @@ -3587,110 +3660,66 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "body", key: "messageID" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/session/{sessionID}", + return (options?.client ?? this.client).post({ + url: "/api/session/{sessionID}/fork", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Update session - * - * Update properties of an existing session, such as title or other metadata. + * Unshare session */ - public update( + public unshare( parameters: { sessionID: string - directory?: string - workspace?: string - title?: string - metadata?: { - [key: string]: unknown - } - permission?: PermissionRuleset - time?: { - archived?: number - } }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "title" }, - { in: "body", key: "metadata" }, - { in: "body", key: "permission" }, - { in: "body", key: "time" }, - ], - }, - ], - ) - return (options?.client ?? this.client).patch({ - url: "/session/{sessionID}", + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).delete({ + url: "/api/session/{sessionID}/share", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * Get session children - * - * Retrieve all child sessions that were forked from the specified parent session. + * Share session */ - public children( + public share( parameters: { sessionID: string - directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "/session/{sessionID}/children", + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).post({ + url: "/api/session/{sessionID}/share", ...options, ...params, }) } /** - * Get session todos + * Switch session agent * - * Retrieve the todo list associated with a specific session, showing tasks and action items. + * Switch the agent used by subsequent provider turns. */ - public todo( + public switchAgent( parameters: { sessionID: string - directory?: string - workspace?: string + agent?: string }, options?: Options, ) { @@ -3700,30 +3729,36 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "body", key: "agent" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/session/{sessionID}/todo", + return (options?.client ?? this.client).post< + V2SessionSwitchAgentResponses, + V2SessionSwitchAgentErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/agent", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Get message diff + * Switch session model * - * Get the file changes (diff) that resulted from a specific user message in the session. + * Switch the model used by subsequent provider turns. */ - public diff( + public switchModel( parameters: { sessionID: string - directory?: string - workspace?: string - messageID?: string + model?: ModelRef }, options?: Options, ) { @@ -3733,32 +3768,40 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "query", key: "messageID" }, + { in: "body", key: "model" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/session/{sessionID}/diff", + return (options?.client ?? this.client).post< + V2SessionSwitchModelResponses, + V2SessionSwitchModelErrors, + ThrowOnError + >({ + url: "/api/session/{sessionID}/model", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Get session messages + * Send message * - * Retrieve all messages in a session, including user prompts and AI responses. + * Durably admit one session input and schedule agent-loop execution unless resume is false. */ - public messages( + public prompt( parameters: { sessionID: string - directory?: string - workspace?: string - limit?: number - before?: string + id?: string + prompt?: Prompt + payload?: SessionInputPayload + delivery?: "steer" | "queue" + resume?: boolean }, options?: Options, ) { @@ -3768,45 +3811,41 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "query", key: "limit" }, - { in: "query", key: "before" }, + { in: "body", key: "id" }, + { in: "body", key: "prompt" }, + { in: "body", key: "payload" }, + { in: "body", key: "delivery" }, + { in: "body", key: "resume" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/session/{sessionID}/message", + return (options?.client ?? this.client).post({ + url: "/api/session/{sessionID}/prompt", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Send message + * Run session command * - * Create and send a new message to a session, streaming the AI response. + * Expand a configured command into one durable current-session input and schedule execution. */ - public prompt( + public command( parameters: { sessionID: string - directory?: string - workspace?: string - messageID?: string - model?: { - providerID: string - modelID: string - } - agent?: string - noReply?: boolean - tools?: { - [key: string]: boolean - } - format?: OutputFormat - system?: string - variant?: string - parts?: Array + id?: string + name?: string + arguments?: string + payload?: SessionInputPayload + delivery?: "steer" | "queue" + resume?: boolean }, options?: Options, ) { @@ -3816,23 +3855,18 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "messageID" }, - { in: "body", key: "model" }, - { in: "body", key: "agent" }, - { in: "body", key: "noReply" }, - { in: "body", key: "tools" }, - { in: "body", key: "format" }, - { in: "body", key: "system" }, - { in: "body", key: "variant" }, - { in: "body", key: "parts" }, + { in: "body", key: "id" }, + { in: "body", key: "name" }, + { in: "body", key: "arguments" }, + { in: "body", key: "payload" }, + { in: "body", key: "delivery" }, + { in: "body", key: "resume" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/message", + return (options?.client ?? this.client).post({ + url: "/api/session/{sessionID}/command", ...options, ...params, headers: { @@ -3844,16 +3878,14 @@ export class Session2 extends HeyApiClient { } /** - * Delete message + * Run a session shell command * - * Permanently delete a specific message and all of its parts from a session without reverting file changes. + * Run a shell command at the Session location and record its output in current Session history. */ - public deleteMessage( + public shell( parameters: { sessionID: string - messageID: string - directory?: string - workspace?: string + command?: string }, options?: Options, ) { @@ -3863,142 +3895,90 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "path", key: "messageID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "body", key: "command" }, ], }, ], ) - return (options?.client ?? this.client).delete< - SessionDeleteMessageResponses, - SessionDeleteMessageErrors, - ThrowOnError - >({ - url: "/session/{sessionID}/message/{messageID}", + return (options?.client ?? this.client).post({ + url: "/api/session/{sessionID}/shell", ...options, ...params, - }) - } - - /** - * Get message + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + /** + * Compact session * - * Retrieve a specific message from a session by its message ID. + * Compact a session conversation. */ - public message( + public compact( parameters: { sessionID: string - messageID: string - directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "messageID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "/session/{sessionID}/message/{messageID}", + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).post({ + url: "/api/session/{sessionID}/compact", ...options, ...params, }) } /** - * Fork session + * Wait for session * - * Create a new session by forking an existing session at a specific message point. + * Wait for a session agent loop to become idle. */ - public fork( + public wait( parameters: { sessionID: string - directory?: string - workspace?: string - messageID?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "messageID" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/fork", + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).post({ + url: "/api/session/{sessionID}/wait", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * Abort session + * Get session context * - * Abort an active session and stop any ongoing AI processing or command execution. + * Retrieve the active context messages for a session (all messages after the last compaction). */ - public abort( + public context( parameters: { sessionID: string - directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/abort", + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/session/{sessionID}/context", ...options, ...params, }) } /** - * Initialize session + * Get session history * - * Analyze the current application and create an AGENTS.md file with project-specific agent configurations. + * Read one finite page of public durable Session events after an exclusive aggregate sequence. Newly committed events may appear on later pages. */ - public init( + public history( parameters: { sessionID: string - directory?: string - workspace?: string - modelID?: string - providerID?: string - messageID?: string + limit?: number + after?: number }, options?: Options, ) { @@ -4008,37 +3988,28 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "modelID" }, - { in: "body", key: "providerID" }, - { in: "body", key: "messageID" }, + { in: "query", key: "limit" }, + { in: "query", key: "after" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/init", + return (options?.client ?? this.client).get({ + url: "/api/session/{sessionID}/history", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * Unshare session + * Subscribe to session events * - * Remove the shareable link for a session, making it private again. + * Replay durable events after an aggregate sequence, then continue with new durable events. */ - public unshare( + public events( parameters: { sessionID: string - directory?: string - workspace?: string + after?: string }, options?: Options, ) { @@ -4048,64 +4019,46 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "query", key: "after" }, ], }, ], ) - return (options?.client ?? this.client).delete({ - url: "/session/{sessionID}/share", + return (options?.client ?? this.client).sse.get({ + url: "/api/session/{sessionID}/event", ...options, ...params, }) } /** - * Share session + * Interrupt session execution * - * Create a shareable link for a session, allowing others to view the conversation. + * Interrupt active execution owned by this OpenCode process. Idle interruption is a no-op. */ - public share( + public interrupt( parameters: { sessionID: string - directory?: string - workspace?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/share", + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) + return (options?.client ?? this.client).post({ + url: "/api/session/{sessionID}/interrupt", ...options, ...params, }) } /** - * Summarize session + * Get session message * - * Generate a concise summary of the session using AI compaction to preserve key information. + * Retrieve one projected message owned by the Session. */ - public summarize( + public message( parameters: { sessionID: string - directory?: string - workspace?: string - providerID?: string - modelID?: string - auto?: boolean + messageID: string }, options?: Options, ) { @@ -4115,51 +4068,29 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "providerID" }, - { in: "body", key: "modelID" }, - { in: "body", key: "auto" }, + { in: "path", key: "messageID" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/summarize", + return (options?.client ?? this.client).get({ + url: "/api/session/{sessionID}/message/{messageID}", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * Send async message + * Get session messages * - * Create and send a new message to a session asynchronously, starting the session if needed and returning immediately. + * Retrieve projected messages for a session. throughSeq is an atomic replay boundary for a subsequent event subscription. Items keep the requested order across pages; use cursor.next or cursor.previous to move through the ordered timeline. */ - public promptAsync( + public messages( parameters: { sessionID: string - directory?: string - workspace?: string - messageID?: string - model?: { - providerID: string - modelID: string - } - agent?: string - noReply?: boolean - tools?: { - [key: string]: boolean - } - format?: OutputFormat - system?: string - variant?: string - parts?: Array + limit?: number + order?: "asc" | "desc" + cursor?: string }, options?: Options, ) { @@ -4169,57 +4100,122 @@ export class Session2 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "messageID" }, - { in: "body", key: "model" }, - { in: "body", key: "agent" }, - { in: "body", key: "noReply" }, - { in: "body", key: "tools" }, - { in: "body", key: "format" }, - { in: "body", key: "system" }, - { in: "body", key: "variant" }, - { in: "body", key: "parts" }, + { in: "query", key: "limit" }, + { in: "query", key: "order" }, + { in: "query", key: "cursor" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/prompt_async", + return (options?.client ?? this.client).get({ + url: "/api/session/{sessionID}/message", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } - /** - * Send command - * - * Send a new command to a session for execution by the AI assistant. - */ - public command( - parameters: { - sessionID: string - directory?: string - workspace?: string - messageID?: string - agent?: string - model?: string - arguments?: string - command?: string - variant?: string - parts?: Array<{ - id?: string - type: "file" - mime: string - filename?: string - url: string - source?: FilePartSource - }> + private _queue?: Queue + get queue(): Queue { + return (this._queue ??= new Queue({ client: this.client })) + } + + private _revert?: Revert + get revert(): Revert { + return (this._revert ??= new Revert({ client: this.client })) + } + + private _permission?: Permission + get permission(): Permission { + return (this._permission ??= new Permission({ client: this.client })) + } + + private _question?: Question + get question(): Question { + return (this._question ??= new Question({ client: this.client })) + } +} + +export class Model extends HeyApiClient { + /** + * List models + * + * Retrieve available models ordered by release date. + */ + public list( + parameters?: { + location?: { + directory?: string + workspace?: string + } + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/model", + ...options, + ...params, + }) + } + + /** + * Get the default model + * + * Retrieve the configured default model, or the newest available model when none is configured. + */ + public default( + parameters?: { + location?: { + directory?: string + workspace?: string + } + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/model/default", + ...options, + ...params, + }) + } +} + +export class Provider extends HeyApiClient { + /** + * List providers + * + * Retrieve active AI providers so clients can show provider availability and configuration. + */ + public list( + parameters?: { + location?: { + directory?: string + workspace?: string + } + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/provider", + ...options, + ...params, + }) + } + + /** + * Get provider + * + * Retrieve a single AI provider so clients can inspect its availability and endpoint settings. + */ + public get( + parameters: { + providerID: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -4228,49 +4224,35 @@ export class Session2 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "messageID" }, - { in: "body", key: "agent" }, - { in: "body", key: "model" }, - { in: "body", key: "arguments" }, - { in: "body", key: "command" }, - { in: "body", key: "variant" }, - { in: "body", key: "parts" }, + { in: "path", key: "providerID" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/command", + return (options?.client ?? this.client).get({ + url: "/api/provider/{providerID}", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } +} +export class Connect extends HeyApiClient { /** - * Run shell command + * Connect with key * - * Execute a shell command within the session context and return the AI's response. + * Run a key authentication method and store the resulting credential. */ - public shell( + public key( parameters: { - sessionID: string - directory?: string - workspace?: string - messageID?: string - agent?: string - model?: { - providerID: string - modelID: string + integrationID: string + location?: { + directory?: string + workspace?: string } - command?: string + key?: string + label?: string }, options?: Options, ) { @@ -4279,19 +4261,20 @@ export class Session2 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "messageID" }, - { in: "body", key: "agent" }, - { in: "body", key: "model" }, - { in: "body", key: "command" }, + { in: "path", key: "integrationID" }, + { in: "query", key: "location" }, + { in: "body", key: "key" }, + { in: "body", key: "label" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/shell", + return (options?.client ?? this.client).post< + V2IntegrationConnectKeyResponses, + V2IntegrationConnectKeyErrors, + ThrowOnError + >({ + url: "/api/integration/{integrationID}/connect/key", ...options, ...params, headers: { @@ -4303,17 +4286,22 @@ export class Session2 extends HeyApiClient { } /** - * Revert message + * Begin OAuth connection * - * Revert a specific message in a session, undoing its effects and restoring the previous state. + * Start an OAuth attempt and return the authorization details. */ - public revert( + public oauth( parameters: { - sessionID: string - directory?: string - workspace?: string - messageID?: string - partID?: string + integrationID: string + location?: { + directory?: string + workspace?: string + } + methodID?: string + inputs?: { + [key: string]: string + } + label?: string }, options?: Options, ) { @@ -4322,17 +4310,21 @@ export class Session2 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "messageID" }, - { in: "body", key: "partID" }, + { in: "path", key: "integrationID" }, + { in: "query", key: "location" }, + { in: "body", key: "methodID" }, + { in: "body", key: "inputs" }, + { in: "body", key: "label" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/revert", + return (options?.client ?? this.client).post< + V2IntegrationConnectOauthResponses, + V2IntegrationConnectOauthErrors, + ThrowOnError + >({ + url: "/api/integration/{integrationID}/connect/oauth", ...options, ...params, headers: { @@ -4342,17 +4334,21 @@ export class Session2 extends HeyApiClient { }, }) } +} +export class Attempt extends HeyApiClient { /** - * Restore reverted messages + * Cancel OAuth connection * - * Restore all previously reverted messages in a session. + * Cancel an OAuth attempt and release its resources. */ - public unrevert( + public cancel( parameters: { - sessionID: string - directory?: string - workspace?: string + attemptID: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -4361,32 +4357,35 @@ export class Session2 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "path", key: "attemptID" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/session/{sessionID}/unrevert", + return (options?.client ?? this.client).delete< + V2IntegrationAttemptCancelResponses, + V2IntegrationAttemptCancelErrors, + ThrowOnError + >({ + url: "/api/integration/attempt/{attemptID}", ...options, ...params, }) } -} -export class Part extends HeyApiClient { /** - * Delete a part from a message. + * Get OAuth attempt status + * + * Poll the current status of an OAuth attempt. */ - public delete( + public status( parameters: { - sessionID: string - messageID: string - partID: string - directory?: string - workspace?: string + attemptID: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -4395,33 +4394,36 @@ export class Part extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "messageID" }, - { in: "path", key: "partID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "path", key: "attemptID" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).delete({ - url: "/session/{sessionID}/message/{messageID}/part/{partID}", + return (options?.client ?? this.client).get< + V2IntegrationAttemptStatusResponses, + V2IntegrationAttemptStatusErrors, + ThrowOnError + >({ + url: "/api/integration/attempt/{attemptID}", ...options, ...params, }) } /** - * Update a part in a message. + * Complete OAuth connection + * + * Complete a code-based OAuth attempt and store the resulting credential. */ - public update( + public complete( parameters: { - sessionID: string - messageID: string - partID: string - directory?: string - workspace?: string - part?: Part2 + attemptID: string + location?: { + directory?: string + workspace?: string + } + code?: string }, options?: Options, ) { @@ -4430,18 +4432,19 @@ export class Part extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "messageID" }, - { in: "path", key: "partID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { key: "part", map: "body" }, + { in: "path", key: "attemptID" }, + { in: "query", key: "location" }, + { in: "body", key: "code" }, ], }, ], ) - return (options?.client ?? this.client).patch({ - url: "/session/{sessionID}/message/{messageID}/part/{partID}", + return (options?.client ?? this.client).post< + V2IntegrationAttemptCompleteResponses, + V2IntegrationAttemptCompleteErrors, + ThrowOnError + >({ + url: "/api/integration/attempt/{attemptID}/complete", ...options, ...params, headers: { @@ -4453,57 +4456,41 @@ export class Part extends HeyApiClient { } } -export class History extends HeyApiClient { +export class Integration extends HeyApiClient { /** - * List sync events + * List integrations * - * 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. + * Retrieve available integrations and their authentication methods. */ public list( parameters?: { - directory?: string - workspace?: string - body?: { - [key: string]: number + location?: { + directory?: string + workspace?: string } }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { key: "body", map: "body" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/sync/history", + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/integration", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } -} -export class Sync extends HeyApiClient { /** - * Start workspace sync + * Get integration * - * Start sync loops for workspaces in the current project that have active sessions. + * Retrieve one integration and its authentication methods. */ - public start( - parameters?: { - directory?: string - workspace?: string + public get( + parameters: { + integrationID: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -4512,38 +4499,43 @@ export class Sync extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "path", key: "integrationID" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/sync/start", + return (options?.client ?? this.client).get({ + url: "/api/integration/{integrationID}", ...options, ...params, }) } + private _connect?: Connect + get connect(): Connect { + return (this._connect ??= new Connect({ client: this.client })) + } + + private _attempt?: Attempt + get attempt(): Attempt { + return (this._attempt ??= new Attempt({ client: this.client })) + } +} + +export class Credential extends HeyApiClient { /** - * Replay sync events + * Remove credential * - * Validate and replay a complete sync event history. + * Remove a stored integration credential. */ - public replay( - parameters?: { - query_directory?: string - workspace?: string - body_directory?: string - events?: Array<{ - id: string - aggregateID: string - seq: number - type: string - data: { - [key: string]: unknown - } - }> + public remove( + parameters: { + credentialID: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -4552,44 +4544,34 @@ export class Sync extends HeyApiClient { [ { args: [ - { - in: "query", - key: "query_directory", - map: "directory", - }, - { in: "query", key: "workspace" }, - { - in: "body", - key: "body_directory", - map: "directory", - }, - { in: "body", key: "events" }, + { in: "path", key: "credentialID" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/sync/replay", - ...options, - ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, + return (options?.client ?? this.client).delete( + { + url: "/api/credential/{credentialID}", + ...options, + ...params, }, - }) + ) } /** - * Steal session into workspace + * Update credential * - * Update a session to belong to the current workspace through the sync event system. + * Update a stored credential label. */ - public steal( - parameters?: { - directory?: string - workspace?: string - sessionID?: string + public update( + parameters: { + credentialID: string + location?: { + directory?: string + workspace?: string + } + label?: string }, options?: Options, ) { @@ -4598,15 +4580,15 @@ export class Sync extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "sessionID" }, + { in: "path", key: "credentialID" }, + { in: "query", key: "location" }, + { in: "body", key: "label" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/sync/steal", + return (options?.client ?? this.client).patch({ + url: "/api/credential/{credentialID}", ...options, ...params, headers: { @@ -4616,159 +4598,131 @@ export class Sync extends HeyApiClient { }, }) } - - private _history?: History - get history(): History { - return (this._history ??= new History({ client: this.client })) - } } -export class Control extends HeyApiClient { +export class Request extends HeyApiClient { /** - * Get next TUI request + * List pending permission requests * - * Retrieve the next TUI request from the queue for processing. + * Retrieve pending permission requests for a location. */ - public next( + public list( parameters?: { - directory?: string - workspace?: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "/tui/control/next", + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get< + V2PermissionRequestListResponses, + V2PermissionRequestListErrors, + ThrowOnError + >({ + url: "/api/permission/request", ...options, ...params, }) } +} +export class Saved extends HeyApiClient { /** - * Submit TUI response + * List saved permissions * - * Submit a response to the TUI request queue to complete a pending request. + * Retrieve saved permissions, optionally filtered by project. */ - public response( + public list( parameters?: { - directory?: string - workspace?: string - body?: unknown + projectID?: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { key: "body", map: "body" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/tui/control/response", + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "projectID" }] }]) + return (options?.client ?? this.client).get< + V2PermissionSavedListResponses, + V2PermissionSavedListErrors, + ThrowOnError + >({ + url: "/api/permission/saved", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } -} -export class Tui extends HeyApiClient { /** - * Append TUI prompt + * Remove saved permission * - * Append prompt to the TUI. + * Remove a saved permission by ID. */ - public appendPrompt( - parameters?: { - directory?: string - workspace?: string - text?: string + public remove( + parameters: { + id: string }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "text" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/tui/append-prompt", + const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "id" }] }]) + return (options?.client ?? this.client).delete< + V2PermissionSavedRemoveResponses, + V2PermissionSavedRemoveErrors, + ThrowOnError + >({ + url: "/api/permission/saved/{id}", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } +} + +export class Permission2 extends HeyApiClient { + private _request?: Request + get request(): Request { + return (this._request ??= new Request({ client: this.client })) + } + + private _saved?: Saved + get saved(): Saved { + return (this._saved ??= new Saved({ client: this.client })) + } +} +export class Fs extends HeyApiClient { /** - * Open help dialog + * Read file * - * Open the help dialog in the TUI to display user assistance information. + * Serve one file relative to the requested location. */ - public openHelp( + public read( parameters?: { - directory?: string - workspace?: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/tui/open-help", + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/fs/read/*", ...options, ...params, }) } /** - * Open sessions dialog + * List directory * - * Open the session dialog. + * List direct children of one directory relative to the requested location. */ - public openSessions( + public list( parameters?: { - directory?: string - workspace?: string + location?: { + directory?: string + workspace?: string + } + path?: string }, options?: Options, ) { @@ -4777,28 +4731,33 @@ export class Tui extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "query", key: "location" }, + { in: "query", key: "path" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/tui/open-sessions", + return (options?.client ?? this.client).get({ + url: "/api/fs/list", ...options, ...params, }) } /** - * Open themes dialog + * Find files * - * Open the theme dialog. + * Find recursively ranked filesystem entries relative to the requested location. */ - public openThemes( - parameters?: { - directory?: string - workspace?: string + public find( + parameters: { + location?: { + directory?: string + workspace?: string + } + query: string + type?: "file" | "directory" + limit?: string }, options?: Options, ) { @@ -4807,136 +4766,145 @@ export class Tui extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "query", key: "location" }, + { in: "query", key: "query" }, + { in: "query", key: "type" }, + { in: "query", key: "limit" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/tui/open-themes", + return (options?.client ?? this.client).get({ + url: "/api/fs/find", ...options, ...params, }) } +} +export class Command2 extends HeyApiClient { /** - * Open models dialog + * List commands * - * Open the model dialog. + * Retrieve currently registered commands. */ - public openModels( + public list( parameters?: { - directory?: string - workspace?: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/tui/open-models", + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/command", ...options, ...params, }) } +} +export class Skill extends HeyApiClient { /** - * Submit TUI prompt + * List skills * - * Submit the prompt. + * Retrieve currently registered skills. */ - public submitPrompt( + public list( parameters?: { - directory?: string - workspace?: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/tui/submit-prompt", + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/skill", ...options, ...params, }) } +} +export class Event2 extends HeyApiClient { /** - * Clear TUI prompt + * Subscribe to events * - * Clear the prompt. + * Subscribe to native event payloads for the server. */ - public clearPrompt( - parameters?: { - directory?: string - workspace?: string - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/tui/clear-prompt", + public subscribe(options?: Options) { + return (options?.client ?? this.client).sse.get({ + url: "/api/event", ...options, - ...params, }) } +} +export class Pty extends HeyApiClient { /** - * Execute TUI command + * List PTY sessions * - * Execute a TUI command. + * List PTY sessions for a location, including exited sessions retained until removal. */ - public executeCommand( + public list( parameters?: { - directory?: string - workspace?: string - command?: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { - const params = buildClientParams( + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/pty", + ...options, + ...params, + }) + } + + /** + * Create PTY session + * + * Create a pseudo-terminal session for a location. + */ + public create( + parameters?: { + location?: { + directory?: string + workspace?: string + } + command?: string + args?: Array + cwd?: string + title?: string + env?: { + [key: string]: string + } + }, + options?: Options, + ) { + const params = buildClientParams( [parameters], [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, + { in: "query", key: "location" }, { in: "body", key: "command" }, + { in: "body", key: "args" }, + { in: "body", key: "cwd" }, + { in: "body", key: "title" }, + { in: "body", key: "env" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/tui/execute-command", + return (options?.client ?? this.client).post({ + url: "/api/pty", ...options, ...params, headers: { @@ -4948,18 +4916,17 @@ export class Tui extends HeyApiClient { } /** - * Show TUI toast + * Remove PTY session * - * Show a toast notification in the TUI. + * Terminate and remove one PTY session. */ - public showToast( - parameters?: { - directory?: string - workspace?: string - title?: string - message?: string - variant?: "info" | "success" | "warning" | "error" - duration?: number + public remove( + parameters: { + ptyID: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -4968,38 +4935,31 @@ export class Tui extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "title" }, - { in: "body", key: "message" }, - { in: "body", key: "variant" }, - { in: "body", key: "duration" }, + { in: "path", key: "ptyID" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/tui/show-toast", + return (options?.client ?? this.client).delete({ + url: "/api/pty/{ptyID}", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * Publish TUI event + * Get PTY session * - * Publish a TUI event. + * Get one PTY session, including its exit code once exited. */ - public publish( - parameters?: { - directory?: string - workspace?: string - body?: EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow | EventTuiSessionSelect + public get( + parameters: { + ptyID: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -5008,35 +4968,36 @@ export class Tui extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { key: "body", map: "body" }, + { in: "path", key: "ptyID" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/tui/publish", + return (options?.client ?? this.client).get({ + url: "/api/pty/{ptyID}", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * Select session + * Update PTY session * - * Navigate the TUI to display the specified session. + * Update the title or viewport size of one PTY session. */ - public selectSession( - parameters?: { - directory?: string - workspace?: string - sessionID?: string + public update( + parameters: { + ptyID: string + location?: { + directory?: string + workspace?: string + } + title?: string + size?: { + rows: number + cols: number + } }, options?: Options, ) { @@ -5045,15 +5006,16 @@ export class Tui extends HeyApiClient { [ { args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "sessionID" }, + { in: "path", key: "ptyID" }, + { in: "query", key: "location" }, + { in: "body", key: "title" }, + { in: "body", key: "size" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/tui/select-session", + return (options?.client ?? this.client).put({ + url: "/api/pty/{ptyID}", ...options, ...params, headers: { @@ -5064,33 +5026,83 @@ export class Tui extends HeyApiClient { }) } - private _control?: Control - get control(): Control { - return (this._control ??= new Control({ client: this.client })) + /** + * Create PTY WebSocket token + * + * Create a short-lived single-use ticket for opening a PTY WebSocket connection. + */ + public connectToken( + parameters: { + ptyID: string + location?: { + directory?: string + workspace?: string + } + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "location" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/api/pty/{ptyID}/connect-token", + ...options, + ...params, + }) } -} -export class Health extends HeyApiClient { /** - * Check server health + * Connect to PTY session * - * Check whether the API server is ready to accept requests. + * Establish a WebSocket connection streaming PTY output and accepting terminal input. */ - public get(options?: Options) { - return (options?.client ?? this.client).get({ - url: "/api/health", + public connect( + parameters: { + ptyID: string + "location[directory]"?: string + "location[workspace]"?: string + cursor?: string + ticket?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "location[directory]" }, + { in: "query", key: "location[workspace]" }, + { in: "query", key: "cursor" }, + { in: "query", key: "ticket" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/api/pty/{ptyID}/connect", ...options, + ...params, }) } } -export class Location extends HeyApiClient { +export class Request2 extends HeyApiClient { /** - * Get location + * List pending question requests * - * Resolve the requested location or the server default location. + * Retrieve pending question requests for a location. */ - public get( + public list( parameters?: { location?: { directory?: string @@ -5100,19 +5112,30 @@ export class Location extends HeyApiClient { options?: Options, ) { const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/location", + return (options?.client ?? this.client).get< + V2QuestionRequestListResponses, + V2QuestionRequestListErrors, + ThrowOnError + >({ + url: "/api/question/request", ...options, ...params, }) } } -export class Agent extends HeyApiClient { +export class Question2 extends HeyApiClient { + private _request?: Request2 + get request(): Request2 { + return (this._request ??= new Request2({ client: this.client })) + } +} + +export class Reference extends HeyApiClient { /** - * List agents + * List references * - * Retrieve currently registered agents. + * List references available in the requested location. */ public list( parameters?: { @@ -5124,87 +5147,24 @@ export class Agent extends HeyApiClient { options?: Options, ) { const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/agent", + return (options?.client ?? this.client).get({ + url: "/api/reference", ...options, ...params, }) } } -export class Drain extends HeyApiClient { - /** - * Pause queue drain - */ - public pause( +export class ProjectCopy2 extends HeyApiClient { + public remove( parameters: { - sessionID: string - }, - options?: Options, - ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).post< - V2SessionQueueDrainPauseResponses, - V2SessionQueueDrainPauseErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/queue/drain-pause", - ...options, - ...params, - }) - } - - /** - * Resume queue drain - */ - public resume( - parameters: { - sessionID: string - }, - options?: Options, - ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).post< - V2SessionQueueDrainResumeResponses, - V2SessionQueueDrainResumeErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/queue/drain-resume", - ...options, - ...params, - }) - } -} - -export class Queue extends HeyApiClient { - /** - * List queued inputs - */ - public list( - parameters: { - sessionID: string - }, - options?: Options, - ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/session/{sessionID}/queue", - ...options, - ...params, - }) - } - - /** - * Enqueue input - * - * Durably enqueue an input without starting an idle drain unless resume is true. - */ - public enqueue( - parameters: { - sessionID: string - id?: string - payload?: SessionInputPayload - resume?: boolean + projectID: string + location?: { + directory?: string + workspace?: string + } + directory?: string + force?: boolean }, options?: Options, ) { @@ -5213,20 +5173,20 @@ export class Queue extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "body", key: "id" }, - { in: "body", key: "payload" }, - { in: "body", key: "resume" }, + { in: "path", key: "projectID" }, + { in: "query", key: "location" }, + { in: "body", key: "directory" }, + { in: "body", key: "force" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2SessionQueueEnqueueResponses, - V2SessionQueueEnqueueErrors, + return (options?.client ?? this.client).delete< + V2ProjectCopyRemoveResponses, + V2ProjectCopyRemoveErrors, ThrowOnError >({ - url: "/api/session/{sessionID}/queue", + url: "/experimental/project/{projectID}/copy", ...options, ...params, headers: { @@ -5237,13 +5197,16 @@ export class Queue extends HeyApiClient { }) } - /** - * Remove queued input - */ - public remove( + public create( parameters: { - sessionID: string - messageID: string + projectID: string + location?: { + directory?: string + workspace?: string + } + strategy?: string + directory?: string + name?: string }, options?: Options, ) { @@ -5252,30 +5215,36 @@ export class Queue extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "messageID" }, + { in: "path", key: "projectID" }, + { in: "query", key: "location" }, + { in: "body", key: "strategy" }, + { in: "body", key: "directory" }, + { in: "body", key: "name" }, ], }, ], ) - return (options?.client ?? this.client).delete< - V2SessionQueueRemoveResponses, - V2SessionQueueRemoveErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/queue/{messageID}", - ...options, - ...params, - }) + return (options?.client ?? this.client).post( + { + url: "/experimental/project/{projectID}/copy", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }, + ) } - /** - * Get queued input - */ - public get( + public refresh( parameters: { - sessionID: string - messageID: string + projectID: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -5284,27 +5253,35 @@ export class Queue extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "messageID" }, + { in: "path", key: "projectID" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/session/{sessionID}/queue/{messageID}", + return (options?.client ?? this.client).post< + V2ProjectCopyRefreshResponses, + V2ProjectCopyRefreshErrors, + ThrowOnError + >({ + url: "/experimental/project/{projectID}/copy/refresh", ...options, ...params, }) } +} +export class Auth3 extends HeyApiClient { /** - * Update queued input + * Remove MCP OAuth */ - public update( + public remove( parameters: { - sessionID: string - messageID: string - payload?: SessionInputPayload + name: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -5313,39 +5290,29 @@ export class Queue extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "messageID" }, - { in: "body", key: "payload" }, + { in: "path", key: "name" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).patch< - V2SessionQueueUpdateResponses, - V2SessionQueueUpdateErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/queue/{messageID}", + return (options?.client ?? this.client).delete({ + url: "/api/mcp/{name}/auth", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * Send queued input now - * - * Atomically expedite a queued input into steer delivery and wake execution. + * Start MCP OAuth */ - public send( + public start( parameters: { - sessionID: string - messageID: string - payload?: SessionInputPayload + name: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -5354,42 +5321,30 @@ export class Queue extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "messageID" }, - { in: "body", key: "payload" }, + { in: "path", key: "name" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/session/{sessionID}/queue/{messageID}/send", + return (options?.client ?? this.client).post({ + url: "/api/mcp/{name}/auth", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } - private _drain?: Drain - get drain(): Drain { - return (this._drain ??= new Drain({ client: this.client })) - } -} - -export class Revert extends HeyApiClient { /** - * Stage session revert - * - * Stage or move a reversible session boundary and optionally apply its file changes. + * Complete MCP OAuth */ - public stage( + public callback( parameters: { - sessionID: string - messageID?: string - files?: boolean + name: string + location?: { + directory?: string + workspace?: string + } + code?: string }, options?: Options, ) { @@ -5398,19 +5353,15 @@ export class Revert extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "body", key: "messageID" }, - { in: "body", key: "files" }, + { in: "path", key: "name" }, + { in: "query", key: "location" }, + { in: "body", key: "code" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2SessionRevertStageResponses, - V2SessionRevertStageErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/revert/stage", + return (options?.client ?? this.client).post({ + url: "/api/mcp/{name}/auth/callback", ...options, ...params, headers: { @@ -5420,91 +5371,74 @@ export class Revert extends HeyApiClient { }, }) } +} +export class Mcp2 extends HeyApiClient { /** - * Clear staged revert + * Get MCP server status */ - public clear( - parameters: { - sessionID: string - }, - options?: Options, - ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).post< - V2SessionRevertClearResponses, - V2SessionRevertClearErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/revert/clear", - ...options, - ...params, - }) - } - - /** - * Commit staged revert - */ - public commit( - parameters: { - sessionID: string - }, - options?: Options, - ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).post< - V2SessionRevertCommitResponses, - V2SessionRevertCommitErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/revert/commit", - ...options, - ...params, - }) - } -} - -export class Permission2 extends HeyApiClient { - /** - * List session permission requests - * - * Retrieve pending permission requests owned by a session. - */ - public list( - parameters: { - sessionID: string + public status( + parameters?: { + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).get< - V2SessionPermissionListResponses, - V2SessionPermissionListErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/permission", + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) + return (options?.client ?? this.client).get({ + url: "/api/mcp", ...options, ...params, }) } /** - * Create permission request - * - * Evaluate and, when approval is required, create a permission request for a session. + * Add an MCP server */ - public create( - parameters: { - sessionID: string - id?: string - action?: string - resources?: Array - save?: Array - metadata?: { - [key: string]: unknown + public add( + parameters?: { + location?: { + directory?: string + workspace?: string } - source?: PermissionV2Source - agent?: string + name?: string + server?: + | { + type: "local" + command: Array + cwd?: string + environment?: { + [key: string]: string + } + disabled?: boolean + timeout?: { + startup?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" + request?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" + } + } + | { + type: "remote" + url: string + headers?: { + [key: string]: string + } + oauth?: + | { + client_id?: string + client_secret?: string + scope?: string + callback_port?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" + redirect_uri?: string + } + | false + disabled?: boolean + timeout?: { + startup?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" + request?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" + } + } }, options?: Options, ) { @@ -5513,24 +5447,15 @@ export class Permission2 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "body", key: "id" }, - { in: "body", key: "action" }, - { in: "body", key: "resources" }, - { in: "body", key: "save" }, - { in: "body", key: "metadata" }, - { in: "body", key: "source" }, - { in: "body", key: "agent" }, + { in: "query", key: "location" }, + { in: "body", key: "name" }, + { in: "body", key: "server" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2SessionPermissionCreateResponses, - V2SessionPermissionCreateErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/permission", + return (options?.client ?? this.client).post({ + url: "/api/mcp", ...options, ...params, headers: { @@ -5542,14 +5467,15 @@ export class Permission2 extends HeyApiClient { } /** - * Get permission request - * - * Retrieve a pending permission request owned by a session. + * Connect an MCP server */ - public get( + public connect( parameters: { - sessionID: string - requestID: string + name: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -5558,34 +5484,29 @@ export class Permission2 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "requestID" }, + { in: "path", key: "name" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).get< - V2SessionPermissionGetResponses, - V2SessionPermissionGetErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/permission/{requestID}", + return (options?.client ?? this.client).post({ + url: "/api/mcp/{name}/connect", ...options, ...params, }) } /** - * Reply to pending permission request - * - * Respond to a pending permission request owned by a session. + * Disconnect an MCP server */ - public reply( + public disconnect( parameters: { - sessionID: string - requestID: string - reply?: PermissionV2Reply - message?: string + name: string + location?: { + directory?: string + workspace?: string + } }, options?: Options, ) { @@ -5594,65 +5515,60 @@ export class Permission2 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "requestID" }, - { in: "body", key: "reply" }, - { in: "body", key: "message" }, + { in: "path", key: "name" }, + { in: "query", key: "location" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2SessionPermissionReplyResponses, - V2SessionPermissionReplyErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/permission/{requestID}/reply", + return (options?.client ?? this.client).post({ + url: "/api/mcp/{name}/disconnect", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } -} -export class Question2 extends HeyApiClient { /** - * List session question requests - * - * Retrieve pending question requests owned by a session. + * List MCP resources */ - public list( - parameters: { - sessionID: string + public resources( + parameters?: { + location?: { + directory?: string + workspace?: string + } + server?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).get< - V2SessionQuestionListResponses, - V2SessionQuestionListErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/question", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "location" }, + { in: "query", key: "server" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/api/mcp/resource", ...options, ...params, }) } /** - * Reply to pending question request - * - * Answer a pending question request owned by a session. + * List MCP resource templates */ - public reply( - parameters: { - sessionID: string - requestID: string - questionV2Reply: QuestionV2Reply + public resourceTemplates( + parameters?: { + location?: { + directory?: string + workspace?: string + } + server?: string }, options?: Options, ) { @@ -5661,38 +5577,34 @@ export class Question2 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "requestID" }, - { key: "questionV2Reply", map: "body" }, + { in: "query", key: "location" }, + { in: "query", key: "server" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2SessionQuestionReplyResponses, - V2SessionQuestionReplyErrors, + return (options?.client ?? this.client).get< + V2McpResourceTemplatesResponses, + V2McpResourceTemplatesErrors, ThrowOnError >({ - url: "/api/session/{sessionID}/question/{requestID}/reply", + url: "/api/mcp/resource-template", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * Reject pending question request - * - * Reject a pending question request owned by a session. + * Read an MCP resource */ - public reject( - parameters: { - sessionID: string - requestID: string + public resourceRead( + parameters?: { + location?: { + directory?: string + workspace?: string + } + server?: string + uri?: string }, options?: Options, ) { @@ -5701,40 +5613,138 @@ export class Question2 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "requestID" }, + { in: "query", key: "location" }, + { in: "body", key: "server" }, + { in: "body", key: "uri" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2SessionQuestionRejectResponses, - V2SessionQuestionRejectErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/question/{requestID}/reject", + return (options?.client ?? this.client).post({ + url: "/api/mcp/resource/read", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } + + private _auth?: Auth3 + get auth(): Auth3 { + return (this._auth ??= new Auth3({ client: this.client })) + } } -export class Session3 extends HeyApiClient { +export class V2 extends HeyApiClient { + private _project?: Project2 + get project(): Project2 { + return (this._project ??= new Project2({ client: this.client })) + } + + private _health?: Health + get health(): Health { + return (this._health ??= new Health({ client: this.client })) + } + + private _location?: Location + get location(): Location { + return (this._location ??= new Location({ client: this.client })) + } + + private _agent?: Agent + get agent(): Agent { + return (this._agent ??= new Agent({ client: this.client })) + } + + private _session?: Session2 + get session(): Session2 { + return (this._session ??= new Session2({ client: this.client })) + } + + private _model?: Model + get model(): Model { + return (this._model ??= new Model({ client: this.client })) + } + + private _provider?: Provider + get provider(): Provider { + return (this._provider ??= new Provider({ client: this.client })) + } + + private _integration?: Integration + get integration(): Integration { + return (this._integration ??= new Integration({ client: this.client })) + } + + private _credential?: Credential + get credential(): Credential { + return (this._credential ??= new Credential({ client: this.client })) + } + + private _permission?: Permission2 + get permission(): Permission2 { + return (this._permission ??= new Permission2({ client: this.client })) + } + + private _fs?: Fs + get fs(): Fs { + return (this._fs ??= new Fs({ client: this.client })) + } + + private _command?: Command2 + get command(): Command2 { + return (this._command ??= new Command2({ client: this.client })) + } + + private _skill?: Skill + get skill(): Skill { + return (this._skill ??= new Skill({ client: this.client })) + } + + private _event?: Event2 + get event(): Event2 { + return (this._event ??= new Event2({ client: this.client })) + } + + private _pty?: Pty + get pty(): Pty { + return (this._pty ??= new Pty({ client: this.client })) + } + + private _question?: Question2 + get question(): Question2 { + return (this._question ??= new Question2({ client: this.client })) + } + + private _reference?: Reference + get reference(): Reference { + return (this._reference ??= new Reference({ client: this.client })) + } + + private _projectCopy?: ProjectCopy2 + get projectCopy(): ProjectCopy2 { + return (this._projectCopy ??= new ProjectCopy2({ client: this.client })) + } + + private _mcp?: Mcp2 + get mcp(): Mcp2 { + return (this._mcp ??= new Mcp2({ client: this.client })) + } +} + +export class Pty2 extends HeyApiClient { /** - * List sessions + * List available shells * - * Retrieve sessions in the requested order. Items keep that order across pages; use cursor.next or cursor.previous to move through the ordered list. + * Get a list of available shells on the system. */ - public list( + public shells( parameters?: { - workspace?: string - limit?: number - order?: "asc" | "desc" - search?: string directory?: string - project?: string - subpath?: string - cursor?: string + workspace?: string }, options?: Options, ) { @@ -5743,36 +5753,28 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "query", key: "workspace" }, - { in: "query", key: "limit" }, - { in: "query", key: "order" }, - { in: "query", key: "search" }, { in: "query", key: "directory" }, - { in: "query", key: "project" }, - { in: "query", key: "subpath" }, - { in: "query", key: "cursor" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/session", + return (options?.client ?? this.client).get({ + url: "/pty/shells", ...options, ...params, }) } /** - * Create session + * List PTY sessions * - * Create a session at the requested location. + * Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode. */ - public create( + public list( parameters?: { - id?: string - agent?: string - model?: ModelRef - location?: LocationRef + directory?: string + workspace?: string }, options?: Options, ) { @@ -5781,64 +5783,35 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "body", key: "id" }, - { in: "body", key: "agent" }, - { in: "body", key: "model" }, - { in: "body", key: "location" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/session", + return (options?.client ?? this.client).get({ + url: "/pty", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, - }) - } - - /** - * List active sessions - * - * Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive. - */ - public active(options?: Options) { - return (options?.client ?? this.client).get({ - url: "/api/session/active", - ...options, }) } /** - * Get session + * Create PTY session * - * Retrieve a session by ID. - */ - public get( - parameters: { - sessionID: string - }, - options?: Options, - ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/session/{sessionID}", - ...options, - ...params, - }) - } - - /** - * Update session + * Create a new pseudo-terminal (PTY) session for running shell commands and processes. */ - public update( - parameters: { - sessionID: string + public create( + parameters?: { + directory?: string + workspace?: string + command?: string + args?: Array + cwd?: string title?: string + env?: { + [key: string]: string + } }, options?: Options, ) { @@ -5847,14 +5820,19 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "command" }, + { in: "body", key: "args" }, + { in: "body", key: "cwd" }, { in: "body", key: "title" }, + { in: "body", key: "env" }, ], }, ], ) - return (options?.client ?? this.client).patch({ - url: "/api/session/{sessionID}", + return (options?.client ?? this.client).post({ + url: "/pty", ...options, ...params, headers: { @@ -5866,12 +5844,15 @@ export class Session3 extends HeyApiClient { } /** - * Fork session + * Remove PTY session + * + * Remove and terminate a specific pseudo-terminal (PTY) session. */ - public fork( + public remove( parameters: { - sessionID: string - messageID?: string + ptyID: string + directory?: string + workspace?: string }, options?: Options, ) { @@ -5880,67 +5861,67 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "body", key: "messageID" }, + { in: "path", key: "ptyID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/session/{sessionID}/fork", - ...options, - ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, - }) - } - - /** - * Unshare session - */ - public unshare( - parameters: { - sessionID: string - }, - options?: Options, - ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).delete({ - url: "/api/session/{sessionID}/share", + return (options?.client ?? this.client).delete({ + url: "/pty/{ptyID}", ...options, ...params, }) } /** - * Share session + * Get PTY session + * + * Retrieve detailed information about a specific pseudo-terminal (PTY) session. */ - public share( + public get( parameters: { - sessionID: string + ptyID: string + directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).post({ - url: "/api/session/{sessionID}/share", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "ptyID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/pty/{ptyID}", ...options, ...params, }) } /** - * Switch session agent + * Update PTY session * - * Switch the agent used by subsequent provider turns. + * Update properties of an existing pseudo-terminal (PTY) session. */ - public switchAgent( + public update( parameters: { - sessionID: string - agent?: string + ptyID: string + directory?: string + workspace?: string + title?: string + size?: { + rows: number + cols: number + } }, options?: Options, ) { @@ -5949,18 +5930,17 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "body", key: "agent" }, + { in: "path", key: "ptyID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "title" }, + { in: "body", key: "size" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2SessionSwitchAgentResponses, - V2SessionSwitchAgentErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/agent", + return (options?.client ?? this.client).put({ + url: "/pty/{ptyID}", ...options, ...params, headers: { @@ -5972,14 +5952,15 @@ export class Session3 extends HeyApiClient { } /** - * Switch session model + * Create PTY WebSocket token * - * Switch the model used by subsequent provider turns. + * Create a short-lived ticket for opening a PTY WebSocket connection. */ - public switchModel( + public connectToken( parameters: { - sessionID: string - model?: ModelRef + ptyID: string + directory?: string + workspace?: string }, options?: Options, ) { @@ -5988,41 +5969,32 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "body", key: "model" }, + { in: "path", key: "ptyID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2SessionSwitchModelResponses, - V2SessionSwitchModelErrors, - ThrowOnError - >({ - url: "/api/session/{sessionID}/model", + return (options?.client ?? this.client).post({ + url: "/pty/{ptyID}/connect-token", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * Send message + * Connect to PTY session * - * Durably admit one session input and schedule agent-loop execution unless resume is false. + * Establish a WebSocket connection to interact with a pseudo-terminal (PTY) session in real-time. */ - public prompt( + public connect( parameters: { - sessionID: string - id?: string - prompt?: Prompt - payload?: SessionInputPayload - delivery?: "steer" | "queue" - resume?: boolean + ptyID: string + directory?: string + workspace?: string + cursor?: string + ticket?: string }, options?: Options, ) { @@ -6031,42 +6003,33 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "body", key: "id" }, - { in: "body", key: "prompt" }, - { in: "body", key: "payload" }, - { in: "body", key: "delivery" }, - { in: "body", key: "resume" }, + { in: "path", key: "ptyID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "query", key: "cursor" }, + { in: "query", key: "ticket" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/session/{sessionID}/prompt", + return (options?.client ?? this.client).get({ + url: "/pty/{ptyID}/connect", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } +} +export class Question3 extends HeyApiClient { /** - * Run session command + * List pending questions * - * Expand a configured command into one durable current-session input and schedule execution. + * Get all pending question requests across all sessions. */ - public command( - parameters: { - sessionID: string - id?: string - name?: string - arguments?: string - payload?: SessionInputPayload - delivery?: "steer" | "queue" - resume?: boolean + public list( + parameters?: { + directory?: string + workspace?: string }, options?: Options, ) { @@ -6075,38 +6038,30 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "body", key: "id" }, - { in: "body", key: "name" }, - { in: "body", key: "arguments" }, - { in: "body", key: "payload" }, - { in: "body", key: "delivery" }, - { in: "body", key: "resume" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/session/{sessionID}/command", + return (options?.client ?? this.client).get({ + url: "/question", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * Run a session shell command + * Reply to question request * - * Run a shell command at the Session location and record its output in current Session history. + * Provide answers to a question request from the AI assistant. */ - public shell( + public reply( parameters: { - sessionID: string - command?: string + requestID: string + directory?: string + workspace?: string + answers?: Array }, options?: Options, ) { @@ -6115,14 +6070,16 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "body", key: "command" }, + { in: "path", key: "requestID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "answers" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/session/{sessionID}/shell", + return (options?.client ?? this.client).post({ + url: "/question/{requestID}/reply", ...options, ...params, headers: { @@ -6134,72 +6091,124 @@ export class Session3 extends HeyApiClient { } /** - * Compact session + * Reject question request * - * Compact a session conversation. + * Reject a question request from the AI assistant. */ - public compact( + public reject( parameters: { - sessionID: string + requestID: string + directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).post({ - url: "/api/session/{sessionID}/compact", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "requestID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/question/{requestID}/reject", ...options, ...params, }) } +} +export class Permission3 extends HeyApiClient { /** - * Wait for session + * List pending permissions * - * Wait for a session agent loop to become idle. + * Get all pending permission requests across all sessions. */ - public wait( - parameters: { - sessionID: string + public list( + parameters?: { + directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).post({ - url: "/api/session/{sessionID}/wait", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/permission", ...options, ...params, }) } /** - * Get session context + * Respond to permission request * - * Retrieve the active context messages for a session (all messages after the last compaction). + * Approve or deny a permission request from the AI assistant. */ - public context( + public reply( parameters: { - sessionID: string + requestID: string + directory?: string + workspace?: string + reply?: "once" | "always" | "reject" + message?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/session/{sessionID}/context", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "requestID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "reply" }, + { in: "body", key: "message" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/permission/{requestID}/reply", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Get session history + * Respond to permission * - * Read one finite page of public durable Session events after an exclusive aggregate sequence. Newly committed events may appear on later pages. + * Approve or deny a permission request from the AI assistant. + * + * @deprecated */ - public history( + public respond( parameters: { sessionID: string - limit?: number - after?: number + permissionID: string + directory?: string + workspace?: string + response?: "once" | "always" | "reject" }, options?: Options, ) { @@ -6209,28 +6218,42 @@ export class Session3 extends HeyApiClient { { args: [ { in: "path", key: "sessionID" }, - { in: "query", key: "limit" }, - { in: "query", key: "after" }, + { in: "path", key: "permissionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "response" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/session/{sessionID}/history", + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/permissions/{permissionID}", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } +} +export class Oauth extends HeyApiClient { /** - * Subscribe to session events + * Start OAuth authorization * - * Replay durable events after an aggregate sequence, then continue with new durable events. + * Start the OAuth authorization flow for a provider. */ - public events( + public authorize( parameters: { - sessionID: string - after?: string + providerID: string + directory?: string + workspace?: string + method?: number + inputs?: { + [key: string]: string + } }, options?: Options, ) { @@ -6239,47 +6262,87 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "after" }, + { in: "path", key: "providerID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "method" }, + { in: "body", key: "inputs" }, ], }, ], ) - return (options?.client ?? this.client).sse.get({ - url: "/api/session/{sessionID}/event", + return (options?.client ?? this.client).post< + ProviderOauthAuthorizeResponses, + ProviderOauthAuthorizeErrors, + ThrowOnError + >({ + url: "/provider/{providerID}/oauth/authorize", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Interrupt session execution + * Handle OAuth callback * - * Interrupt active execution owned by this OpenCode process. Idle interruption is a no-op. + * Handle the OAuth callback from a provider after user authorization. */ - public interrupt( + public callback( parameters: { - sessionID: string + providerID: string + directory?: string + workspace?: string + method?: number + code?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }]) - return (options?.client ?? this.client).post({ - url: "/api/session/{sessionID}/interrupt", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "providerID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "method" }, + { in: "body", key: "code" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post< + ProviderOauthCallbackResponses, + ProviderOauthCallbackErrors, + ThrowOnError + >({ + url: "/provider/{providerID}/oauth/callback", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } +} +export class Provider2 extends HeyApiClient { /** - * Get session message + * List providers * - * Retrieve one projected message owned by the Session. + * Get a list of all available AI providers, including both available and connected ones. */ - public message( - parameters: { - sessionID: string - messageID: string + public list( + parameters?: { + directory?: string + workspace?: string }, options?: Options, ) { @@ -6288,30 +6351,28 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "path", key: "messageID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/session/{sessionID}/message/{messageID}", + return (options?.client ?? this.client).get({ + url: "/provider", ...options, ...params, }) } /** - * Get session messages + * Get provider auth methods * - * Retrieve projected messages for a session. throughSeq is an atomic replay boundary for a subsequent event subscription. Items keep the requested order across pages; use cursor.next or cursor.previous to move through the ordered timeline. + * Retrieve available authentication methods for all AI providers. */ - public messages( - parameters: { - sessionID: string - limit?: number - order?: "asc" | "desc" - cursor?: string + public auth( + parameters?: { + directory?: string + workspace?: string }, options?: Options, ) { @@ -6320,101 +6381,41 @@ export class Session3 extends HeyApiClient { [ { args: [ - { in: "path", key: "sessionID" }, - { in: "query", key: "limit" }, - { in: "query", key: "order" }, - { in: "query", key: "cursor" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/session/{sessionID}/message", + return (options?.client ?? this.client).get({ + url: "/provider/auth", ...options, ...params, }) } - private _queue?: Queue - get queue(): Queue { - return (this._queue ??= new Queue({ client: this.client })) - } - - private _revert?: Revert - get revert(): Revert { - return (this._revert ??= new Revert({ client: this.client })) - } - - private _permission?: Permission2 - get permission(): Permission2 { - return (this._permission ??= new Permission2({ client: this.client })) - } - - private _question?: Question2 - get question(): Question2 { - return (this._question ??= new Question2({ client: this.client })) + private _oauth?: Oauth + get oauth(): Oauth { + return (this._oauth ??= new Oauth({ client: this.client })) } } -export class Model extends HeyApiClient { +export class Session3 extends HeyApiClient { /** - * List models + * List sessions * - * Retrieve available models ordered by release date. + * Get a list of all OpenCode sessions, sorted by most recently updated. */ public list( parameters?: { - location?: { - directory?: string - workspace?: string - } - }, - options?: Options, - ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/model", - ...options, - ...params, - }) - } -} - -export class Provider2 extends HeyApiClient { - /** - * List providers - * - * Retrieve active AI providers so clients can show provider availability and configuration. - */ - public list( - parameters?: { - location?: { - directory?: string - workspace?: string - } - }, - options?: Options, - ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/provider", - ...options, - ...params, - }) - } - - /** - * Get provider - * - * Retrieve a single AI provider so clients can inspect its availability and endpoint settings. - */ - public get( - parameters: { - providerID: string - location?: { - directory?: string - workspace?: string - } + directory?: string + workspace?: string + scope?: "project" + path?: string + roots?: boolean | "true" | "false" + start?: number + search?: string + limit?: number }, options?: Options, ) { @@ -6423,35 +6424,47 @@ export class Provider2 extends HeyApiClient { [ { args: [ - { in: "path", key: "providerID" }, - { in: "query", key: "location" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "query", key: "scope" }, + { in: "query", key: "path" }, + { in: "query", key: "roots" }, + { in: "query", key: "start" }, + { in: "query", key: "search" }, + { in: "query", key: "limit" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/provider/{providerID}", + return (options?.client ?? this.client).get({ + url: "/session", ...options, ...params, }) } -} -export class Connect extends HeyApiClient { /** - * Connect with key + * Create session * - * Run a key authentication method and store the resulting credential. + * Create a new OpenCode session for interacting with AI assistants and managing conversations. */ - public key( - parameters: { - integrationID: string - location?: { - directory?: string - workspace?: string + public create( + parameters?: { + directory?: string + workspace?: string + parentID?: string + title?: string + agent?: string + model?: { + id: string + providerID: string + variant?: string } - key?: string - label?: string + metadata?: { + [key: string]: unknown + } + permission?: PermissionRuleset + workspaceID?: string }, options?: Options, ) { @@ -6460,20 +6473,21 @@ export class Connect extends HeyApiClient { [ { args: [ - { in: "path", key: "integrationID" }, - { in: "query", key: "location" }, - { in: "body", key: "key" }, - { in: "body", key: "label" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "parentID" }, + { in: "body", key: "title" }, + { in: "body", key: "agent" }, + { in: "body", key: "model" }, + { in: "body", key: "metadata" }, + { in: "body", key: "permission" }, + { in: "body", key: "workspaceID" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2IntegrationConnectKeyResponses, - V2IntegrationConnectKeyErrors, - ThrowOnError - >({ - url: "/api/integration/{integrationID}/connect/key", + return (options?.client ?? this.client).post({ + url: "/session", ...options, ...params, headers: { @@ -6485,22 +6499,14 @@ export class Connect extends HeyApiClient { } /** - * Begin OAuth connection + * Get session status * - * Start an OAuth attempt and return the authorization details. + * Retrieve the current status of all sessions, including active, idle, and completed states. */ - public oauth( - parameters: { - integrationID: string - location?: { - directory?: string - workspace?: string - } - methodID?: string - inputs?: { - [key: string]: string - } - label?: string + public status( + parameters?: { + directory?: string + workspace?: string }, options?: Options, ) { @@ -6509,45 +6515,29 @@ export class Connect extends HeyApiClient { [ { args: [ - { in: "path", key: "integrationID" }, - { in: "query", key: "location" }, - { in: "body", key: "methodID" }, - { in: "body", key: "inputs" }, - { in: "body", key: "label" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2IntegrationConnectOauthResponses, - V2IntegrationConnectOauthErrors, - ThrowOnError - >({ - url: "/api/integration/{integrationID}/connect/oauth", + return (options?.client ?? this.client).get({ + url: "/session/status", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } -} -export class Attempt extends HeyApiClient { /** - * Cancel OAuth connection + * Delete session * - * Cancel an OAuth attempt and release its resources. + * Delete a session and permanently remove all associated data, including messages and history. */ - public cancel( + public delete( parameters: { - attemptID: string - location?: { - directory?: string - workspace?: string - } + sessionID: string + directory?: string + workspace?: string }, options?: Options, ) { @@ -6556,35 +6546,30 @@ export class Attempt extends HeyApiClient { [ { args: [ - { in: "path", key: "attemptID" }, - { in: "query", key: "location" }, + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).delete< - V2IntegrationAttemptCancelResponses, - V2IntegrationAttemptCancelErrors, - ThrowOnError - >({ - url: "/api/integration/attempt/{attemptID}", + return (options?.client ?? this.client).delete({ + url: "/session/{sessionID}", ...options, ...params, }) } /** - * Get OAuth attempt status + * Get session * - * Poll the current status of an OAuth attempt. + * Retrieve detailed information about a specific OpenCode session. */ - public status( + public get( parameters: { - attemptID: string - location?: { - directory?: string - workspace?: string - } + sessionID: string + directory?: string + workspace?: string }, options?: Options, ) { @@ -6593,36 +6578,38 @@ export class Attempt extends HeyApiClient { [ { args: [ - { in: "path", key: "attemptID" }, - { in: "query", key: "location" }, + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).get< - V2IntegrationAttemptStatusResponses, - V2IntegrationAttemptStatusErrors, - ThrowOnError - >({ - url: "/api/integration/attempt/{attemptID}", + return (options?.client ?? this.client).get({ + url: "/session/{sessionID}", ...options, ...params, }) } /** - * Complete OAuth connection + * Update session * - * Complete a code-based OAuth attempt and store the resulting credential. + * Update properties of an existing session, such as title or other metadata. */ - public complete( + public update( parameters: { - attemptID: string - location?: { - directory?: string - workspace?: string + sessionID: string + directory?: string + workspace?: string + title?: string + metadata?: { + [key: string]: unknown + } + permission?: PermissionRuleset + time?: { + archived?: number } - code?: string }, options?: Options, ) { @@ -6631,19 +6618,19 @@ export class Attempt extends HeyApiClient { [ { args: [ - { in: "path", key: "attemptID" }, - { in: "query", key: "location" }, - { in: "body", key: "code" }, + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "title" }, + { in: "body", key: "metadata" }, + { in: "body", key: "permission" }, + { in: "body", key: "time" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2IntegrationAttemptCompleteResponses, - V2IntegrationAttemptCompleteErrors, - ThrowOnError - >({ - url: "/api/integration/attempt/{attemptID}/complete", + return (options?.client ?? this.client).patch({ + url: "/session/{sessionID}", ...options, ...params, headers: { @@ -6653,43 +6640,49 @@ export class Attempt extends HeyApiClient { }, }) } -} -export class Integration extends HeyApiClient { /** - * List integrations + * Get session children * - * Retrieve available integrations and their authentication methods. + * Retrieve all child sessions that were forked from the specified parent session. */ - public list( - parameters?: { - location?: { - directory?: string - workspace?: string - } + public children( + parameters: { + sessionID: string + directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/integration", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/session/{sessionID}/children", ...options, ...params, }) } /** - * Get integration + * Get session todos * - * Retrieve one integration and its authentication methods. + * Retrieve the todo list associated with a specific session, showing tasks and action items. */ - public get( + public todo( parameters: { - integrationID: string - location?: { - directory?: string - workspace?: string - } + sessionID: string + directory?: string + workspace?: string }, options?: Options, ) { @@ -6698,43 +6691,66 @@ export class Integration extends HeyApiClient { [ { args: [ - { in: "path", key: "integrationID" }, - { in: "query", key: "location" }, + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/integration/{integrationID}", + return (options?.client ?? this.client).get({ + url: "/session/{sessionID}/todo", ...options, ...params, }) } - private _connect?: Connect - get connect(): Connect { - return (this._connect ??= new Connect({ client: this.client })) - } - - private _attempt?: Attempt - get attempt(): Attempt { - return (this._attempt ??= new Attempt({ client: this.client })) + /** + * Get message diff + * + * Get the file changes (diff) that resulted from a specific user message in the session. + */ + public diff( + parameters: { + sessionID: string + directory?: string + workspace?: string + messageID?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "query", key: "messageID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/session/{sessionID}/diff", + ...options, + ...params, + }) } -} -export class Credential extends HeyApiClient { /** - * Remove credential + * Get session messages * - * Remove a stored integration credential. + * Retrieve all messages in a session, including user prompts and AI responses. */ - public remove( + public messages( parameters: { - credentialID: string - location?: { - directory?: string - workspace?: string - } + sessionID: string + directory?: string + workspace?: string + limit?: number + before?: string }, options?: Options, ) { @@ -6743,34 +6759,46 @@ export class Credential extends HeyApiClient { [ { args: [ - { in: "path", key: "credentialID" }, - { in: "query", key: "location" }, + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "query", key: "limit" }, + { in: "query", key: "before" }, ], }, ], ) - return (options?.client ?? this.client).delete( - { - url: "/api/credential/{credentialID}", - ...options, - ...params, - }, - ) + return (options?.client ?? this.client).get({ + url: "/session/{sessionID}/message", + ...options, + ...params, + }) } /** - * Update credential + * Send message * - * Update a stored credential label. + * Create and send a new message to a session, streaming the AI response. */ - public update( + public prompt( parameters: { - credentialID: string - location?: { - directory?: string - workspace?: string + sessionID: string + directory?: string + workspace?: string + messageID?: string + model?: { + providerID: string + modelID: string } - label?: string + agent?: string + noReply?: boolean + tools?: { + [key: string]: boolean + } + format?: OutputFormat + system?: string + variant?: string + parts?: Array }, options?: Options, ) { @@ -6779,15 +6807,24 @@ export class Credential extends HeyApiClient { [ { args: [ - { in: "path", key: "credentialID" }, - { in: "query", key: "location" }, - { in: "body", key: "label" }, + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "messageID" }, + { in: "body", key: "model" }, + { in: "body", key: "agent" }, + { in: "body", key: "noReply" }, + { in: "body", key: "tools" }, + { in: "body", key: "format" }, + { in: "body", key: "system" }, + { in: "body", key: "variant" }, + { in: "body", key: "parts" }, ], }, ], ) - return (options?.client ?? this.client).patch({ - url: "/api/credential/{credentialID}", + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/message", ...options, ...params, headers: { @@ -6797,131 +6834,163 @@ export class Credential extends HeyApiClient { }, }) } -} -export class Request extends HeyApiClient { /** - * List pending permission requests + * Delete message * - * Retrieve pending permission requests for a location. + * Permanently delete a specific message and all of its parts from a session without reverting file changes. */ - public list( - parameters?: { - location?: { - directory?: string - workspace?: string - } + public deleteMessage( + parameters: { + sessionID: string + messageID: string + directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get< - V2PermissionRequestListResponses, - V2PermissionRequestListErrors, + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "path", key: "messageID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).delete< + SessionDeleteMessageResponses, + SessionDeleteMessageErrors, ThrowOnError >({ - url: "/api/permission/request", + url: "/session/{sessionID}/message/{messageID}", ...options, ...params, }) } -} -export class Saved extends HeyApiClient { /** - * List saved permissions + * Get message * - * Retrieve saved permissions, optionally filtered by project. + * Retrieve a specific message from a session by its message ID. */ - public list( - parameters?: { - projectID?: string + public message( + parameters: { + sessionID: string + messageID: string + directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "projectID" }] }]) - return (options?.client ?? this.client).get< - V2PermissionSavedListResponses, - V2PermissionSavedListErrors, - ThrowOnError - >({ - url: "/api/permission/saved", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "path", key: "messageID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/session/{sessionID}/message/{messageID}", ...options, ...params, }) } /** - * Remove saved permission + * Fork session * - * Remove a saved permission by ID. + * Create a new session by forking an existing session at a specific message point. */ - public remove( + public fork( parameters: { - id: string + sessionID: string + directory?: string + workspace?: string + messageID?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "id" }] }]) - return (options?.client ?? this.client).delete< - V2PermissionSavedRemoveResponses, - V2PermissionSavedRemoveErrors, - ThrowOnError - >({ - url: "/api/permission/saved/{id}", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "messageID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/fork", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } -} -export class Permission3 extends HeyApiClient { - private _request?: Request - get request(): Request { - return (this._request ??= new Request({ client: this.client })) - } - - private _saved?: Saved - get saved(): Saved { - return (this._saved ??= new Saved({ client: this.client })) - } -} - -export class Fs extends HeyApiClient { /** - * Read file + * Abort session * - * Serve one file relative to the requested location. + * Abort an active session and stop any ongoing AI processing or command execution. */ - public read( - parameters?: { - location?: { - directory?: string - workspace?: string - } + public abort( + parameters: { + sessionID: string + directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/fs/read/*", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/abort", ...options, ...params, }) } /** - * List directory + * Initialize session * - * List direct children of one directory relative to the requested location. + * Analyze the current application and create an AGENTS.md file with project-specific agent configurations. */ - public list( - parameters?: { - location?: { - directory?: string - workspace?: string - } - path?: string + public init( + parameters: { + sessionID: string + directory?: string + workspace?: string + modelID?: string + providerID?: string + messageID?: string }, options?: Options, ) { @@ -6930,33 +6999,38 @@ export class Fs extends HeyApiClient { [ { args: [ - { in: "query", key: "location" }, - { in: "query", key: "path" }, + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "modelID" }, + { in: "body", key: "providerID" }, + { in: "body", key: "messageID" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/fs/list", + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/init", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Find files + * Unshare session * - * Find recursively ranked filesystem entries relative to the requested location. + * Remove the shareable link for a session, making it private again. */ - public find( + public unshare( parameters: { - location?: { - directory?: string - workspace?: string - } - query: string - type?: "file" | "directory" - limit?: string + sessionID: string + directory?: string + workspace?: string }, options?: Options, ) { @@ -6965,125 +7039,230 @@ export class Fs extends HeyApiClient { [ { args: [ - { in: "query", key: "location" }, - { in: "query", key: "query" }, - { in: "query", key: "type" }, - { in: "query", key: "limit" }, + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/fs/find", + return (options?.client ?? this.client).delete({ + url: "/session/{sessionID}/share", ...options, ...params, }) } -} -export class Command2 extends HeyApiClient { /** - * List commands + * Share session * - * Retrieve currently registered commands. + * Create a shareable link for a session, allowing others to view the conversation. */ - public list( - parameters?: { - location?: { - directory?: string - workspace?: string - } + public share( + parameters: { + sessionID: string + directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/command", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/share", ...options, ...params, }) } -} -export class Skill extends HeyApiClient { /** - * List skills + * Summarize session * - * Retrieve currently registered skills. + * Generate a concise summary of the session using AI compaction to preserve key information. */ - public list( - parameters?: { - location?: { - directory?: string - workspace?: string - } + public summarize( + parameters: { + sessionID: string + directory?: string + workspace?: string + providerID?: string + modelID?: string + auto?: boolean }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/skill", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "providerID" }, + { in: "body", key: "modelID" }, + { in: "body", key: "auto" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/summarize", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } -} -export class Event2 extends HeyApiClient { /** - * Subscribe to events + * Send async message * - * Subscribe to native event payloads for the server. + * Create and send a new message to a session asynchronously, starting the session if needed and returning immediately. */ - public subscribe(options?: Options) { - return (options?.client ?? this.client).sse.get({ - url: "/api/event", + public promptAsync( + parameters: { + sessionID: string + directory?: string + workspace?: string + messageID?: string + model?: { + providerID: string + modelID: string + } + agent?: string + noReply?: boolean + tools?: { + [key: string]: boolean + } + format?: OutputFormat + system?: string + variant?: string + parts?: Array + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "messageID" }, + { in: "body", key: "model" }, + { in: "body", key: "agent" }, + { in: "body", key: "noReply" }, + { in: "body", key: "tools" }, + { in: "body", key: "format" }, + { in: "body", key: "system" }, + { in: "body", key: "variant" }, + { in: "body", key: "parts" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/prompt_async", ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } -} -export class Pty2 extends HeyApiClient { /** - * List PTY sessions + * Send command * - * List PTY sessions for a location, including exited sessions retained until removal. + * Send a new command to a session for execution by the AI assistant. */ - public list( - parameters?: { - location?: { - directory?: string - workspace?: string - } + public command( + parameters: { + sessionID: string + directory?: string + workspace?: string + messageID?: string + agent?: string + model?: string + arguments?: string + command?: string + variant?: string + parts?: Array<{ + id?: string + type: "file" + mime: string + filename?: string + url: string + source?: FilePartSource + }> }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/pty", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "messageID" }, + { in: "body", key: "agent" }, + { in: "body", key: "model" }, + { in: "body", key: "arguments" }, + { in: "body", key: "command" }, + { in: "body", key: "variant" }, + { in: "body", key: "parts" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/command", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Create PTY session + * Run shell command * - * Create a pseudo-terminal session for a location. + * Execute a shell command within the session context and return the AI's response. */ - public create( - parameters?: { - location?: { - directory?: string - workspace?: string + public shell( + parameters: { + sessionID: string + directory?: string + workspace?: string + messageID?: string + agent?: string + model?: { + providerID: string + modelID: string } command?: string - args?: Array - cwd?: string - title?: string - env?: { - [key: string]: string - } }, options?: Options, ) { @@ -7092,18 +7271,19 @@ export class Pty2 extends HeyApiClient { [ { args: [ - { in: "query", key: "location" }, + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "messageID" }, + { in: "body", key: "agent" }, + { in: "body", key: "model" }, { in: "body", key: "command" }, - { in: "body", key: "args" }, - { in: "body", key: "cwd" }, - { in: "body", key: "title" }, - { in: "body", key: "env" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/pty", + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/shell", ...options, ...params, headers: { @@ -7115,17 +7295,17 @@ export class Pty2 extends HeyApiClient { } /** - * Remove PTY session + * Revert message * - * Terminate and remove one PTY session. + * Revert a specific message in a session, undoing its effects and restoring the previous state. */ - public remove( + public revert( parameters: { - ptyID: string - location?: { - directory?: string - workspace?: string - } + sessionID: string + directory?: string + workspace?: string + messageID?: string + partID?: string }, options?: Options, ) { @@ -7134,31 +7314,106 @@ export class Pty2 extends HeyApiClient { [ { args: [ - { in: "path", key: "ptyID" }, - { in: "query", key: "location" }, + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "messageID" }, + { in: "body", key: "partID" }, ], }, ], ) - return (options?.client ?? this.client).delete({ - url: "/api/pty/{ptyID}", + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/revert", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Get PTY session + * Restore reverted messages * - * Get one PTY session, including its exit code once exited. + * Restore all previously reverted messages in a session. */ - public get( + public unrevert( parameters: { - ptyID: string - location?: { - directory?: string - workspace?: string - } + sessionID: string + directory?: string + workspace?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/session/{sessionID}/unrevert", + ...options, + ...params, + }) + } +} + +export class Part extends HeyApiClient { + /** + * Delete a part from a message. + */ + public delete( + parameters: { + sessionID: string + messageID: string + partID: string + directory?: string + workspace?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "sessionID" }, + { in: "path", key: "messageID" }, + { in: "path", key: "partID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).delete({ + url: "/session/{sessionID}/message/{messageID}/part/{partID}", + ...options, + ...params, + }) + } + + /** + * Update a part in a message. + */ + public update( + parameters: { + sessionID: string + messageID: string + partID: string + directory?: string + workspace?: string + part?: Part2 }, options?: Options, ) { @@ -7167,35 +7422,41 @@ export class Pty2 extends HeyApiClient { [ { args: [ - { in: "path", key: "ptyID" }, - { in: "query", key: "location" }, + { in: "path", key: "sessionID" }, + { in: "path", key: "messageID" }, + { in: "path", key: "partID" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { key: "part", map: "body" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/pty/{ptyID}", + return (options?.client ?? this.client).patch({ + url: "/session/{sessionID}/message/{messageID}/part/{partID}", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } +} +export class History extends HeyApiClient { /** - * Update PTY session + * List sync events * - * Update the title or viewport size of one PTY session. + * 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. */ - public update( - parameters: { - ptyID: string - location?: { - directory?: string - workspace?: string - } - title?: string - size?: { - rows: number - cols: number + public list( + parameters?: { + directory?: string + workspace?: string + body?: { + [key: string]: number } }, options?: Options, @@ -7205,16 +7466,15 @@ export class Pty2 extends HeyApiClient { [ { args: [ - { in: "path", key: "ptyID" }, - { in: "query", key: "location" }, - { in: "body", key: "title" }, - { in: "body", key: "size" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { key: "body", map: "body" }, ], }, ], ) - return (options?.client ?? this.client).put({ - url: "/api/pty/{ptyID}", + return (options?.client ?? this.client).post({ + url: "/sync/history", ...options, ...params, headers: { @@ -7224,19 +7484,18 @@ export class Pty2 extends HeyApiClient { }, }) } +} +export class Sync extends HeyApiClient { /** - * Create PTY WebSocket token + * Start workspace sync * - * Create a short-lived single-use ticket for opening a PTY WebSocket connection. + * Start sync loops for workspaces in the current project that have active sessions. */ - public connectToken( - parameters: { - ptyID: string - location?: { - directory?: string - workspace?: string - } + public start( + parameters?: { + directory?: string + workspace?: string }, options?: Options, ) { @@ -7245,31 +7504,38 @@ export class Pty2 extends HeyApiClient { [ { args: [ - { in: "path", key: "ptyID" }, - { in: "query", key: "location" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/pty/{ptyID}/connect-token", + return (options?.client ?? this.client).post({ + url: "/sync/start", ...options, ...params, }) } /** - * Connect to PTY session + * Replay sync events * - * Establish a WebSocket connection streaming PTY output and accepting terminal input. + * Validate and replay a complete sync event history. */ - public connect( - parameters: { - ptyID: string - "location[directory]"?: string - "location[workspace]"?: string - cursor?: string - ticket?: string + public replay( + parameters?: { + query_directory?: string + workspace?: string + body_directory?: string + events?: Array<{ + id: string + aggregateID: string + seq: number + type: string + data: { + [key: string]: unknown + } + }> }, options?: Options, ) { @@ -7278,92 +7544,87 @@ export class Pty2 extends HeyApiClient { [ { args: [ - { in: "path", key: "ptyID" }, - { in: "query", key: "location[directory]" }, - { in: "query", key: "location[workspace]" }, - { in: "query", key: "cursor" }, - { in: "query", key: "ticket" }, + { + in: "query", + key: "query_directory", + map: "directory", + }, + { in: "query", key: "workspace" }, + { + in: "body", + key: "body_directory", + map: "directory", + }, + { in: "body", key: "events" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/pty/{ptyID}/connect", + return (options?.client ?? this.client).post({ + url: "/sync/replay", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } -} -export class Request2 extends HeyApiClient { /** - * List pending question requests + * Steal session into workspace * - * Retrieve pending question requests for a location. + * Update a session to belong to the current workspace through the sync event system. */ - public list( + public steal( parameters?: { - location?: { - directory?: string - workspace?: string - } + directory?: string + workspace?: string + sessionID?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get< - V2QuestionRequestListResponses, - V2QuestionRequestListErrors, - ThrowOnError - >({ - url: "/api/question/request", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "sessionID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/sync/steal", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } -} -export class Question3 extends HeyApiClient { - private _request?: Request2 - get request(): Request2 { - return (this._request ??= new Request2({ client: this.client })) + private _history?: History + get history(): History { + return (this._history ??= new History({ client: this.client })) } } -export class Reference extends HeyApiClient { +export class Control extends HeyApiClient { /** - * List references + * Get next TUI request * - * List references available in the requested location. + * Retrieve the next TUI request from the queue for processing. */ - public list( + public next( parameters?: { - location?: { - directory?: string - workspace?: string - } - }, - options?: Options, - ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/reference", - ...options, - ...params, - }) - } -} - -export class ProjectCopy2 extends HeyApiClient { - public remove( - parameters: { - projectID: string - location?: { - directory?: string - workspace?: string - } directory?: string - force?: boolean + workspace?: string }, options?: Options, ) { @@ -7372,40 +7633,29 @@ export class ProjectCopy2 extends HeyApiClient { [ { args: [ - { in: "path", key: "projectID" }, - { in: "query", key: "location" }, - { in: "body", key: "directory" }, - { in: "body", key: "force" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).delete< - V2ProjectCopyRemoveResponses, - V2ProjectCopyRemoveErrors, - ThrowOnError - >({ - url: "/experimental/project/{projectID}/copy", + return (options?.client ?? this.client).get({ + url: "/tui/control/next", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } - public create( - parameters: { - projectID: string - location?: { - directory?: string - workspace?: string - } - strategy?: string + /** + * Submit TUI response + * + * Submit a response to the TUI request queue to complete a pending request. + */ + public response( + parameters?: { directory?: string - name?: string + workspace?: string + body?: unknown }, options?: Options, ) { @@ -7414,36 +7664,37 @@ export class ProjectCopy2 extends HeyApiClient { [ { args: [ - { in: "path", key: "projectID" }, - { in: "query", key: "location" }, - { in: "body", key: "strategy" }, - { in: "body", key: "directory" }, - { in: "body", key: "name" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { key: "body", map: "body" }, ], }, ], ) - return (options?.client ?? this.client).post( - { - url: "/experimental/project/{projectID}/copy", - ...options, - ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, + return (options?.client ?? this.client).post({ + url: "/tui/control/response", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, }, - ) + }) } +} - public refresh( - parameters: { - projectID: string - location?: { - directory?: string - workspace?: string - } +export class Tui extends HeyApiClient { + /** + * Append TUI prompt + * + * Append prompt to the TUI. + */ + public appendPrompt( + parameters?: { + directory?: string + workspace?: string + text?: string }, options?: Options, ) { @@ -7452,35 +7703,34 @@ export class ProjectCopy2 extends HeyApiClient { [ { args: [ - { in: "path", key: "projectID" }, - { in: "query", key: "location" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "text" }, ], }, ], ) - return (options?.client ?? this.client).post< - V2ProjectCopyRefreshResponses, - V2ProjectCopyRefreshErrors, - ThrowOnError - >({ - url: "/experimental/project/{projectID}/copy/refresh", + return (options?.client ?? this.client).post({ + url: "/tui/append-prompt", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } -} -export class Auth3 extends HeyApiClient { /** - * Remove MCP OAuth + * Open help dialog + * + * Open the help dialog in the TUI to display user assistance information. */ - public remove( - parameters: { - name: string - location?: { - directory?: string - workspace?: string - } + public openHelp( + parameters?: { + directory?: string + workspace?: string }, options?: Options, ) { @@ -7489,29 +7739,28 @@ export class Auth3 extends HeyApiClient { [ { args: [ - { in: "path", key: "name" }, - { in: "query", key: "location" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).delete({ - url: "/api/mcp/{name}/auth", + return (options?.client ?? this.client).post({ + url: "/tui/open-help", ...options, ...params, }) } /** - * Start MCP OAuth + * Open sessions dialog + * + * Open the session dialog. */ - public start( - parameters: { - name: string - location?: { - directory?: string - workspace?: string - } + public openSessions( + parameters?: { + directory?: string + workspace?: string }, options?: Options, ) { @@ -7520,30 +7769,28 @@ export class Auth3 extends HeyApiClient { [ { args: [ - { in: "path", key: "name" }, - { in: "query", key: "location" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/mcp/{name}/auth", + return (options?.client ?? this.client).post({ + url: "/tui/open-sessions", ...options, ...params, }) } /** - * Complete MCP OAuth + * Open themes dialog + * + * Open the theme dialog. */ - public callback( - parameters: { - name: string - location?: { - directory?: string - workspace?: string - } - code?: string + public openThemes( + parameters?: { + directory?: string + workspace?: string }, options?: Options, ) { @@ -7552,92 +7799,58 @@ export class Auth3 extends HeyApiClient { [ { args: [ - { in: "path", key: "name" }, - { in: "query", key: "location" }, - { in: "body", key: "code" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/mcp/{name}/auth/callback", + return (options?.client ?? this.client).post({ + url: "/tui/open-themes", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } -} -export class Mcp2 extends HeyApiClient { /** - * Get MCP server status + * Open models dialog + * + * Open the model dialog. */ - public status( + public openModels( parameters?: { - location?: { - directory?: string - workspace?: string - } + directory?: string + workspace?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "location" }] }]) - return (options?.client ?? this.client).get({ - url: "/api/mcp", + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/tui/open-models", ...options, ...params, }) } /** - * Add an MCP server + * Submit TUI prompt + * + * Submit the prompt. */ - public add( + public submitPrompt( parameters?: { - location?: { - directory?: string - workspace?: string - } - name?: string - server?: - | { - type: "local" - command: Array - cwd?: string - environment?: { - [key: string]: string - } - disabled?: boolean - timeout?: { - startup?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - request?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - } - } - | { - type: "remote" - url: string - headers?: { - [key: string]: string - } - oauth?: - | { - client_id?: string - client_secret?: string - scope?: string - callback_port?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - redirect_uri?: string - } - | false - disabled?: boolean - timeout?: { - startup?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - request?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - } - } + directory?: string + workspace?: string }, options?: Options, ) { @@ -7646,35 +7859,28 @@ export class Mcp2 extends HeyApiClient { [ { args: [ - { in: "query", key: "location" }, - { in: "body", key: "name" }, - { in: "body", key: "server" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/mcp", + return (options?.client ?? this.client).post({ + url: "/tui/submit-prompt", ...options, ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, }) } /** - * Connect an MCP server + * Clear TUI prompt + * + * Clear the prompt. */ - public connect( - parameters: { - name: string - location?: { - directory?: string - workspace?: string - } + public clearPrompt( + parameters?: { + directory?: string + workspace?: string }, options?: Options, ) { @@ -7683,29 +7889,29 @@ export class Mcp2 extends HeyApiClient { [ { args: [ - { in: "path", key: "name" }, - { in: "query", key: "location" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/mcp/{name}/connect", + return (options?.client ?? this.client).post({ + url: "/tui/clear-prompt", ...options, ...params, }) } /** - * Disconnect an MCP server + * Execute TUI command + * + * Execute a TUI command. */ - public disconnect( - parameters: { - name: string - location?: { - directory?: string - workspace?: string - } + public executeCommand( + parameters?: { + directory?: string + workspace?: string + command?: string }, options?: Options, ) { @@ -7714,29 +7920,38 @@ export class Mcp2 extends HeyApiClient { [ { args: [ - { in: "path", key: "name" }, - { in: "query", key: "location" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "command" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/mcp/{name}/disconnect", + return (options?.client ?? this.client).post({ + url: "/tui/execute-command", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * List MCP resources + * Show TUI toast + * + * Show a toast notification in the TUI. */ - public resources( + public showToast( parameters?: { - location?: { - directory?: string - workspace?: string - } - server?: string + directory?: string + workspace?: string + title?: string + message?: string + variant?: "info" | "success" | "warning" | "error" + duration?: number }, options?: Options, ) { @@ -7745,29 +7960,38 @@ export class Mcp2 extends HeyApiClient { [ { args: [ - { in: "query", key: "location" }, - { in: "query", key: "server" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "title" }, + { in: "body", key: "message" }, + { in: "body", key: "variant" }, + { in: "body", key: "duration" }, ], }, ], ) - return (options?.client ?? this.client).get({ - url: "/api/mcp/resource", + return (options?.client ?? this.client).post({ + url: "/tui/show-toast", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * List MCP resource templates + * Publish TUI event + * + * Publish a TUI event. */ - public resourceTemplates( + public publish( parameters?: { - location?: { - directory?: string - workspace?: string - } - server?: string + directory?: string + workspace?: string + body?: EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow | EventTuiSessionSelect }, options?: Options, ) { @@ -7776,34 +8000,35 @@ export class Mcp2 extends HeyApiClient { [ { args: [ - { in: "query", key: "location" }, - { in: "query", key: "server" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { key: "body", map: "body" }, ], }, ], ) - return (options?.client ?? this.client).get< - V2McpResourceTemplatesResponses, - V2McpResourceTemplatesErrors, - ThrowOnError - >({ - url: "/api/mcp/resource-template", + return (options?.client ?? this.client).post({ + url: "/tui/publish", ...options, ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, }) } /** - * Read an MCP resource + * Select session + * + * Navigate the TUI to display the specified session. */ - public resourceRead( + public selectSession( parameters?: { - location?: { - directory?: string - workspace?: string - } - server?: string - uri?: string + directory?: string + workspace?: string + sessionID?: string }, options?: Options, ) { @@ -7812,15 +8037,15 @@ export class Mcp2 extends HeyApiClient { [ { args: [ - { in: "query", key: "location" }, - { in: "body", key: "server" }, - { in: "body", key: "uri" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "sessionID" }, ], }, ], ) - return (options?.client ?? this.client).post({ - url: "/api/mcp/resource/read", + return (options?.client ?? this.client).post({ + url: "/tui/select-session", ...options, ...params, headers: { @@ -7831,101 +8056,9 @@ export class Mcp2 extends HeyApiClient { }) } - private _auth?: Auth3 - get auth(): Auth3 { - return (this._auth ??= new Auth3({ client: this.client })) - } -} - -export class V2 extends HeyApiClient { - private _health?: Health - get health(): Health { - return (this._health ??= new Health({ client: this.client })) - } - - private _location?: Location - get location(): Location { - return (this._location ??= new Location({ client: this.client })) - } - - private _agent?: Agent - get agent(): Agent { - return (this._agent ??= new Agent({ client: this.client })) - } - - private _session?: Session3 - get session(): Session3 { - return (this._session ??= new Session3({ client: this.client })) - } - - private _model?: Model - get model(): Model { - return (this._model ??= new Model({ client: this.client })) - } - - private _provider?: Provider2 - get provider(): Provider2 { - return (this._provider ??= new Provider2({ client: this.client })) - } - - private _integration?: Integration - get integration(): Integration { - return (this._integration ??= new Integration({ client: this.client })) - } - - private _credential?: Credential - get credential(): Credential { - return (this._credential ??= new Credential({ client: this.client })) - } - - private _permission?: Permission3 - get permission(): Permission3 { - return (this._permission ??= new Permission3({ client: this.client })) - } - - private _fs?: Fs - get fs(): Fs { - return (this._fs ??= new Fs({ client: this.client })) - } - - private _command?: Command2 - get command(): Command2 { - return (this._command ??= new Command2({ client: this.client })) - } - - private _skill?: Skill - get skill(): Skill { - return (this._skill ??= new Skill({ client: this.client })) - } - - private _event?: Event2 - get event(): Event2 { - return (this._event ??= new Event2({ client: this.client })) - } - - private _pty?: Pty2 - get pty(): Pty2 { - return (this._pty ??= new Pty2({ client: this.client })) - } - - private _question?: Question3 - get question(): Question3 { - return (this._question ??= new Question3({ client: this.client })) - } - - private _reference?: Reference - get reference(): Reference { - return (this._reference ??= new Reference({ client: this.client })) - } - - private _projectCopy?: ProjectCopy2 - get projectCopy(): ProjectCopy2 { - return (this._projectCopy ??= new ProjectCopy2({ client: this.client })) - } - - private _mcp?: Mcp2 - get mcp(): Mcp2 { - return (this._mcp ??= new Mcp2({ client: this.client })) + private _control?: Control + get control(): Control { + return (this._control ??= new Control({ client: this.client })) } } @@ -8027,29 +8160,34 @@ export class OpencodeClient extends HeyApiClient { return (this._project ??= new Project({ client: this.client })) } - private _pty?: Pty - get pty(): Pty { - return (this._pty ??= new Pty({ client: this.client })) + private _v2?: V2 + get v2(): V2 { + return (this._v2 ??= new V2({ client: this.client })) } - private _question?: Question - get question(): Question { - return (this._question ??= new Question({ client: this.client })) + private _pty?: Pty2 + get pty(): Pty2 { + return (this._pty ??= new Pty2({ client: this.client })) } - private _permission?: Permission - get permission(): Permission { - return (this._permission ??= new Permission({ client: this.client })) + private _question?: Question3 + get question(): Question3 { + return (this._question ??= new Question3({ client: this.client })) } - private _provider?: Provider - get provider(): Provider { - return (this._provider ??= new Provider({ client: this.client })) + private _permission?: Permission3 + get permission(): Permission3 { + return (this._permission ??= new Permission3({ client: this.client })) } - private _session?: Session2 - get session(): Session2 { - return (this._session ??= new Session2({ client: this.client })) + private _provider?: Provider2 + get provider(): Provider2 { + return (this._provider ??= new Provider2({ client: this.client })) + } + + private _session?: Session3 + get session(): Session3 { + return (this._session ??= new Session3({ client: this.client })) } private _part?: Part @@ -8066,9 +8204,4 @@ export class OpencodeClient extends HeyApiClient { get tui(): Tui { return (this._tui ??= new Tui({ client: this.client })) } - - private _v2?: V2 - get v2(): V2 { - return (this._v2 ??= new V2({ client: this.client })) - } } diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 2f5b0182066f..a0281c6a45c3 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -4431,6 +4431,15 @@ export type ConfigV2ExperimentalPolicy = { resource: string } +export type LocationInfo = { + directory: string + workspaceID?: string + project: { + id: string + directory: string + } +} + export type ProjectDirectories = Array<{ directory: string strategy?: string @@ -4446,15 +4455,6 @@ export type WorkspaceEventConnectionStatus = { status: "connected" | "connecting" | "disconnected" | "error" } -export type LocationInfo = { - directory: string - workspaceID?: string - project: { - id: string - directory: string - } -} - export type ProviderRequest = { headers: { [key: string]: string @@ -9347,6 +9347,41 @@ export type ProjectListResponses = { export type ProjectListResponse = ProjectListResponses[keyof ProjectListResponses] +export type V2ProjectListData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/api/project" +} + +export type V2ProjectListErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError + /** + * Unauthorized + */ + 401: unknown +} + +export type V2ProjectListError = V2ProjectListErrors[keyof V2ProjectListErrors] + +export type V2ProjectListResponses = { + /** + * List of projects + */ + 200: { + location: LocationInfo + data: Array + } +} + +export type V2ProjectListResponse = V2ProjectListResponses[keyof V2ProjectListResponses] + export type ProjectCurrentData = { body?: never path?: never @@ -9375,6 +9410,41 @@ export type ProjectCurrentResponses = { export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses] +export type V2ProjectCurrentData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/api/project/current" +} + +export type V2ProjectCurrentErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError + /** + * Unauthorized + */ + 401: unknown +} + +export type V2ProjectCurrentError = V2ProjectCurrentErrors[keyof V2ProjectCurrentErrors] + +export type V2ProjectCurrentResponses = { + /** + * Current project information + */ + 200: { + location: LocationInfo + data: Project + } +} + +export type V2ProjectCurrentResponse = V2ProjectCurrentResponses[keyof V2ProjectCurrentResponses] + export type ProjectInitGitData = { body?: never path?: never @@ -9471,6 +9541,43 @@ export type ProjectDirectoriesResponses = { export type ProjectDirectoriesResponse = ProjectDirectoriesResponses[keyof ProjectDirectoriesResponses] +export type V2ProjectDirectoriesData = { + body?: never + path: { + projectID: string + } + query?: { + directory?: string + workspace?: string + } + url: "/api/project/{projectID}/directories" +} + +export type V2ProjectDirectoriesErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError + /** + * Unauthorized + */ + 401: unknown +} + +export type V2ProjectDirectoriesError = V2ProjectDirectoriesErrors[keyof V2ProjectDirectoriesErrors] + +export type V2ProjectDirectoriesResponses = { + /** + * Project directories + */ + 200: { + location: LocationInfo + data: ProjectDirectories + } +} + +export type V2ProjectDirectoriesResponse = V2ProjectDirectoriesResponses[keyof V2ProjectDirectoriesResponses] + export type ExperimentalProjectCopyGenerateNameData = { body?: { context?: string @@ -13253,6 +13360,47 @@ export type V2ModelListResponses = { export type V2ModelListResponse = V2ModelListResponses[keyof V2ModelListResponses] +export type V2ModelDefaultData = { + body?: never + path?: never + query?: { + location?: { + directory?: string + workspace?: string + } + } + url: "/api/model/default" +} + +export type V2ModelDefaultErrors = { + /** + * InvalidRequestError + */ + 400: InvalidRequestError + /** + * UnauthorizedError + */ + 401: UnauthorizedError + /** + * ServiceUnavailableError + */ + 503: ServiceUnavailableError +} + +export type V2ModelDefaultError = V2ModelDefaultErrors[keyof V2ModelDefaultErrors] + +export type V2ModelDefaultResponses = { + /** + * Success + */ + 200: { + location: LocationInfo + data: ModelV2Info + } +} + +export type V2ModelDefaultResponse = V2ModelDefaultResponses[keyof V2ModelDefaultResponses] + export type V2ProviderListData = { body?: never path?: never diff --git a/packages/server/src/handlers/message.ts b/packages/server/src/handlers/message.ts index 2a53c2a1910b..358ad808c377 100644 --- a/packages/server/src/handlers/message.ts +++ b/packages/server/src/handlers/message.ts @@ -4,6 +4,7 @@ import { Effect, Schema } from "effect" import { HttpApiBuilder } from "effect/unstable/httpapi" import { Api } from "../api" import { InvalidCursorError, SessionNotFoundError, UnknownError } from "@opencode-ai/protocol/errors" +import { firstPreparedMessageID, stripRepeatedPreparedContext } from "./prepared-context" const DefaultMessagesLimit = 50 @@ -76,10 +77,19 @@ export const MessageHandler = HttpApiBuilder.group(Api, "server.message", (handl ) }), ) - const first = messages[0] - const last = messages.at(-1) + const firstPreparedID = firstPreparedMessageID( + yield* session.context(ctx.params.sessionID).pipe( + Effect.catchTags({ + "Session.MessageDecodeError": () => Effect.succeed([]), + "Session.NotFoundError": () => Effect.succeed([]), + }), + ), + ) + const projected = messages.map((message) => stripRepeatedPreparedContext(message, firstPreparedID)) + const first = projected[0] + const last = projected.at(-1) return { - data: messages, + data: projected, throughSeq, cursor: { previous: first ? cursor.encode(first, order, "previous") : undefined, diff --git a/packages/server/src/handlers/model.ts b/packages/server/src/handlers/model.ts index 36639ae7b1e6..598447ddf73b 100644 --- a/packages/server/src/handlers/model.ts +++ b/packages/server/src/handlers/model.ts @@ -6,12 +6,20 @@ import { response } from "../location" export const ModelHandler = HttpApiBuilder.group(Api, "server.model", (handlers) => Effect.gen(function* () { - return handlers.handle( - "model.list", - Effect.fn(function* () { - const catalog = yield* Catalog.Service - return yield* response(catalog.model.available()) - }), - ) + return handlers + .handle( + "model.list", + Effect.fn(function* () { + const catalog = yield* Catalog.Service + return yield* response(catalog.model.available()) + }), + ) + .handle( + "model.default", + Effect.fn(function* () { + const catalog = yield* Catalog.Service + return yield* response(catalog.model.default().pipe(Effect.map((model) => model ?? null))) + }), + ) }), ) diff --git a/packages/server/src/handlers/prepared-context.ts b/packages/server/src/handlers/prepared-context.ts new file mode 100644 index 000000000000..4e4bfb3be500 --- /dev/null +++ b/packages/server/src/handlers/prepared-context.ts @@ -0,0 +1,16 @@ +import { SessionMessage } from "@opencode-ai/core/session/message" + +const isPreparedAssistant = (message: SessionMessage.Message): message is SessionMessage.Assistant => + message.type === "assistant" && (message.systemPrompt !== undefined || message.toolDefinitions !== undefined) + +export const firstPreparedMessageID = (messages: readonly SessionMessage.Message[]) => + messages.find(isPreparedAssistant)?.id + +export const stripRepeatedPreparedContext = ( + message: SessionMessage.Message, + firstPreparedID: SessionMessage.ID | undefined, +): SessionMessage.Message => { + if (firstPreparedID === undefined || !isPreparedAssistant(message) || message.id === firstPreparedID) return message + const { systemPrompt: _, toolDefinitions: __, ...rest } = message + return rest +} diff --git a/packages/server/src/handlers/session.ts b/packages/server/src/handlers/session.ts index 0e67466feb22..28e89bdaa8fa 100644 --- a/packages/server/src/handlers/session.ts +++ b/packages/server/src/handlers/session.ts @@ -17,6 +17,7 @@ import { } from "@opencode-ai/protocol/errors" import { AbsolutePath } from "@opencode-ai/core/schema" import { SessionSharing } from "@opencode-ai/core/session/share" +import { firstPreparedMessageID, stripRepeatedPreparedContext } from "./prepared-context" const DefaultSessionsLimit = 50 const DefaultSessionHistoryLimit = 50 @@ -114,18 +115,16 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl "session.update", Effect.fn(function* (ctx) { return { - data: yield* session - .update({ sessionID: ctx.params.sessionID, title: ctx.payload.title }) - .pipe( - Effect.catchTag( - "Session.NotFoundError", - (error) => - new SessionNotFoundError({ - sessionID: error.sessionID, - message: `Session not found: ${error.sessionID}`, - }), - ), + data: yield* session.update({ sessionID: ctx.params.sessionID, title: ctx.payload.title }).pipe( + Effect.catchTag( + "Session.NotFoundError", + (error) => + new SessionNotFoundError({ + sessionID: error.sessionID, + message: `Session not found: ${error.sessionID}`, + }), ), + ), } }), ) @@ -133,29 +132,27 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl "session.fork", Effect.fn(function* (ctx) { return { - data: yield* session - .fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload.messageID }) - .pipe( - Effect.catchTag( - "Session.NotFoundError", - (error) => - new SessionNotFoundError({ - sessionID: error.sessionID, - message: `Session not found: ${error.sessionID}`, - }), - ), - Effect.catchTag("Session.MessageDecodeError", (error) => { - const ref = `err_${crypto.randomUUID().slice(0, 8)}` - return Effect.logError("failed to decode session message while forking").pipe( - Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }), - Effect.andThen( - Effect.fail( - new UnknownError({ message: "Unexpected server error. Check server logs for details.", ref }), - ), - ), - ) - }), + data: yield* session.fork({ sessionID: ctx.params.sessionID, messageID: ctx.payload.messageID }).pipe( + Effect.catchTag( + "Session.NotFoundError", + (error) => + new SessionNotFoundError({ + sessionID: error.sessionID, + message: `Session not found: ${error.sessionID}`, + }), ), + Effect.catchTag("Session.MessageDecodeError", (error) => { + const ref = `err_${crypto.randomUUID().slice(0, 8)}` + return Effect.logError("failed to decode session message while forking").pipe( + Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }), + Effect.andThen( + Effect.fail( + new UnknownError({ message: "Unexpected server error. Check server logs for details.", ref }), + ), + ), + ) + }), + ), } }), ) @@ -172,11 +169,14 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl ), ) return { - data: yield* sharing.share(ctx.params.sessionID).pipe( - Effect.mapError( - () => new ServiceUnavailableError({ service: "session.share", message: "Session sharing unavailable" }), + data: yield* sharing + .share(ctx.params.sessionID) + .pipe( + Effect.mapError( + () => + new ServiceUnavailableError({ service: "session.share", message: "Session sharing unavailable" }), + ), ), - ), } }), ) @@ -193,12 +193,14 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl ), ) return { - data: yield* sharing.unshare(ctx.params.sessionID).pipe( - Effect.mapError( - () => - new ServiceUnavailableError({ service: "session.unshare", message: "Session sharing unavailable" }), + data: yield* sharing + .unshare(ctx.params.sessionID) + .pipe( + Effect.mapError( + () => + new ServiceUnavailableError({ service: "session.unshare", message: "Session sharing unavailable" }), + ), ), - ), } }), ) @@ -674,28 +676,30 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl .handle( "session.context", Effect.fn(function* (ctx) { - return { - data: yield* session.context(ctx.params.sessionID).pipe( - Effect.catchTag("Session.NotFoundError", (error) => - Effect.fail( - new SessionNotFoundError({ - sessionID: error.sessionID, - message: `Session not found: ${error.sessionID}`, - }), - ), - ), - Effect.catchTag("Session.MessageDecodeError", (error) => { - const ref = `err_${crypto.randomUUID().slice(0, 8)}` - return Effect.logError("failed to decode session message").pipe( - Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }), - Effect.andThen( - Effect.fail( - new UnknownError({ message: "Unexpected server error. Check server logs for details.", ref }), - ), - ), - ) - }), + const context = yield* session.context(ctx.params.sessionID).pipe( + Effect.catchTag( + "Session.NotFoundError", + (error) => + new SessionNotFoundError({ + sessionID: error.sessionID, + message: `Session not found: ${error.sessionID}`, + }), ), + Effect.catchTag("Session.MessageDecodeError", (error) => { + const ref = `err_${crypto.randomUUID().slice(0, 8)}` + return Effect.logError("failed to decode session message", { error }).pipe( + Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }), + Effect.andThen( + Effect.fail( + new UnknownError({ message: "Unexpected server error. Check server logs for details.", ref }), + ), + ), + ) + }), + ) + const firstPreparedID = firstPreparedMessageID(context) + return { + data: context.map((message) => stripRepeatedPreparedContext(message, firstPreparedID)), } }), ) diff --git a/packages/server/test/prepared-context.test.ts b/packages/server/test/prepared-context.test.ts new file mode 100644 index 000000000000..c121cc55dabb --- /dev/null +++ b/packages/server/test/prepared-context.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, test } from "bun:test" +import { ModelV2 } from "@opencode-ai/core/model" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { SessionMessage } from "@opencode-ai/core/session/message" +import { DateTime } from "effect" +import { firstPreparedMessageID, stripRepeatedPreparedContext } from "../src/handlers/prepared-context" + +const model = { + id: ModelV2.ID.make("model"), + providerID: ProviderV2.ID.make("provider"), +} + +const assistant = (id: string, prepared?: { systemPrompt?: string; toolDefinitions?: string }) => + SessionMessage.Assistant.make({ + id: SessionMessage.ID.make(id), + type: "assistant", + agent: "build", + model, + content: [], + time: { created: DateTime.makeUnsafe(0) }, + ...prepared, + }) + +describe("prepared context projection", () => { + test("finds the first assistant carrying either prepared field", () => { + const unprepared = assistant("msg_unprepared") + const first = assistant("msg_first", { toolDefinitions: "first tools" }) + const later = assistant("msg_later", { systemPrompt: "later system" }) + + expect(firstPreparedMessageID([unprepared, first, later])).toBe(first.id) + }) + + test("retains the first prepared metadata and strips repeated copies", () => { + const first = assistant("msg_first", { systemPrompt: "first system", toolDefinitions: "first tools" }) + const later = assistant("msg_later", { systemPrompt: "later system", toolDefinitions: "later tools" }) + const firstID = firstPreparedMessageID([first, later]) + + expect(stripRepeatedPreparedContext(first, firstID)).toBe(first) + + const projected = stripRepeatedPreparedContext(later, firstID) + expect(projected).toMatchObject({ + id: later.id, + type: "assistant", + content: [], + }) + expect(projected).not.toHaveProperty("systemPrompt") + expect(projected).not.toHaveProperty("toolDefinitions") + expect(later).toHaveProperty("systemPrompt", "later system") + expect(later).toHaveProperty("toolDefinitions", "later tools") + }) +}) From 36cf0fbb141643930f31415a7f3c6b00d1029ca4 Mon Sep 17 00:00:00 2001 From: henry701 Date: Mon, 3 Aug 2026 01:29:47 -0300 Subject: [PATCH 007/129] test(app): cover both health protocol fallbacks --- packages/app/src/utils/server-protocol.test.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/app/src/utils/server-protocol.test.ts b/packages/app/src/utils/server-protocol.test.ts index 0486254b0137..ece9ad278b4c 100644 --- a/packages/app/src/utils/server-protocol.test.ts +++ b/packages/app/src/utils/server-protocol.test.ts @@ -40,12 +40,24 @@ describe("detectServerProtocol", () => { expect(await detectServerProtocol(server, fetcher)).toBe("v2") }) + test("recognizes the legacy V1 global health response", async () => { + const fetcher = mockFetch((input) => { + const path = new URL(input instanceof Request ? input.url : input).pathname + if (path === "/api/session") return Promise.resolve(json({}, 404)) + if (path === "/global/health") return Promise.resolve(json({ healthy: true })) + return Promise.resolve(json({}, 404)) + }) + + expect(await detectServerProtocol(server, fetcher)).toBe("v1") + }) + test("recognizes the transitional V1 API health response", async () => { const fetcher = mockFetch((input) => { const path = new URL(input instanceof Request ? input.url : input).pathname if (path === "/api/session") return Promise.resolve(json({}, 404)) - if (path === "/api/health") return Promise.resolve(json({}, 404)) - return Promise.resolve(json({ healthy: true })) + if (path === "/global/health") return Promise.resolve(json({}, 404)) + if (path === "/api/health") return Promise.resolve(json({ healthy: true })) + return Promise.resolve(json({}, 404)) }) expect(await detectServerProtocol(server, fetcher)).toBe("v1") From 7c1f7ace903ac72e386b68ec16c6c1c1a608679e Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 8 Aug 2026 21:19:27 -0300 Subject: [PATCH 008/129] fix: preserve fork behavior after upstream merge --- .../prompt-from-session-payload.ts | 3 +- packages/app/src/i18n/am.ts | 4 ++ packages/app/src/i18n/az.ts | 4 ++ packages/app/src/i18n/bg.ts | 4 ++ packages/app/src/i18n/bn.ts | 4 ++ packages/app/src/i18n/ca.ts | 4 ++ packages/app/src/i18n/cs.ts | 4 ++ packages/app/src/i18n/dv.ts | 4 ++ packages/app/src/i18n/dz.ts | 4 ++ packages/app/src/i18n/el.ts | 4 ++ packages/app/src/i18n/et.ts | 4 ++ packages/app/src/i18n/fa.ts | 4 ++ packages/app/src/i18n/fi.ts | 4 ++ packages/app/src/i18n/fo.ts | 4 ++ packages/app/src/i18n/hi.ts | 4 ++ packages/app/src/i18n/hr.ts | 4 ++ packages/app/src/i18n/hu.ts | 4 ++ packages/app/src/i18n/hy.ts | 4 ++ packages/app/src/i18n/id.ts | 4 ++ packages/app/src/i18n/is.ts | 4 ++ packages/app/src/i18n/it.ts | 4 ++ packages/app/src/i18n/ka.ts | 4 ++ packages/app/src/i18n/km.ts | 4 ++ packages/app/src/i18n/lo.ts | 4 ++ packages/app/src/i18n/lt.ts | 4 ++ packages/app/src/i18n/lv.ts | 4 ++ packages/app/src/i18n/mk.ts | 4 ++ packages/app/src/i18n/mn.ts | 4 ++ packages/app/src/i18n/ms.ts | 4 ++ packages/app/src/i18n/my.ts | 4 ++ packages/app/src/i18n/ne.ts | 4 ++ packages/app/src/i18n/nl.ts | 4 ++ packages/app/src/i18n/pa.ts | 4 ++ packages/app/src/i18n/ro.ts | 4 ++ packages/app/src/i18n/si.ts | 4 ++ packages/app/src/i18n/sk.ts | 4 ++ packages/app/src/i18n/sl.ts | 4 ++ packages/app/src/i18n/sq.ts | 4 ++ packages/app/src/i18n/sr.ts | 4 ++ packages/app/src/i18n/sv.ts | 4 ++ packages/app/src/i18n/tg.ts | 4 ++ packages/app/src/i18n/tk.ts | 4 ++ packages/app/src/i18n/ur.ts | 4 ++ packages/app/src/i18n/uz.ts | 4 ++ packages/app/src/i18n/vi.ts | 4 ++ packages/opencode/src/provider/error.ts | 28 ++++++++--- packages/opencode/src/session/message-v2.ts | 50 ++++++++++--------- packages/opencode/test/provider/error.test.ts | 48 ++++++++++++++++++ .../opencode/test/session/message-v2.test.ts | 50 +++++++++++++++++++ packages/ui/src/context/marked-code-span.ts | 17 +++++++ packages/ui/src/context/marked-parser.tsx | 2 + .../ui/src/context/marked-regression.test.ts | 3 +- 52 files changed, 343 insertions(+), 34 deletions(-) create mode 100644 packages/opencode/test/provider/error.test.ts create mode 100644 packages/ui/src/context/marked-code-span.ts diff --git a/packages/app/src/components/prompt-input/prompt-from-session-payload.ts b/packages/app/src/components/prompt-input/prompt-from-session-payload.ts index bd961b137239..90be8654ec51 100644 --- a/packages/app/src/components/prompt-input/prompt-from-session-payload.ts +++ b/packages/app/src/components/prompt-input/prompt-from-session-payload.ts @@ -5,6 +5,7 @@ import type { ImageAttachmentPart, Prompt, } from "@/context/prompt" +import { createLegacyBlobReference } from "@/utils/draft-store" type Inline = | { @@ -84,7 +85,7 @@ export function promptFromSessionPayload( id: part.id ?? part.url, filename: part.filename ?? options?.attachmentName ?? "attachment", mime: part.mime, - dataUrl: part.url, + blob: createLegacyBlobReference(part.url), }, ] : [], diff --git a/packages/app/src/i18n/am.ts b/packages/app/src/i18n/am.ts index bda48350e532..832d48bca238 100644 --- a/packages/app/src/i18n/am.ts +++ b/packages/app/src/i18n/am.ts @@ -1127,4 +1127,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} ክፍለ-ጊዜዎች በማህደር ይቀመጣሉ።", "workspace.reset.note": "ይህ workspaceን ከነባሪው ቅርንጫፍ ጋር እንዲመሳሰል ዳግም ያስጀምረዋል።", "dialog.usageExceeded.dontShowAgain": "እንደገና አታሳይ", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/az.ts b/packages/app/src/i18n/az.ts index d818ae57fd6c..76fc4a2e5329 100644 --- a/packages/app/src/i18n/az.ts +++ b/packages/app/src/i18n/az.ts @@ -1165,4 +1165,8 @@ export const dict = { "workspace.reset.archived.one": "1 sessiya arxivlənəcək.", "workspace.reset.archived.many": "{{count}} sessiya arxivlənəcək.", "workspace.reset.note": "Bu iş sahəsini standart branch ilə uyğunlaşdırmaq üçün sıfırlayacaq.", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/bg.ts b/packages/app/src/i18n/bg.ts index 7f63d1fa4b3e..708f04de80a1 100644 --- a/packages/app/src/i18n/bg.ts +++ b/packages/app/src/i18n/bg.ts @@ -1167,4 +1167,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} сесии ще бъдат архивирани.", "workspace.reset.note": "Това ще нулира работното пространство, за да съответства на клона по подразбиране.", "dialog.usageExceeded.dontShowAgain": "Не показвай отново", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/bn.ts b/packages/app/src/i18n/bn.ts index 5f0698ab64f1..a5b0870a5095 100644 --- a/packages/app/src/i18n/bn.ts +++ b/packages/app/src/i18n/bn.ts @@ -1149,4 +1149,8 @@ export const dict: Record = { "workspace.reset.archived.many": "{{count}} সেশন আর্কাইভ করা হবে।", "workspace.reset.note": "এটি ডিফল্ট শাখার সাথে মেলে ওয়ার্কস্পেস রিসেট করবে।", "dialog.usageExceeded.dontShowAgain": "আবার দেখাবেন না", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/ca.ts b/packages/app/src/i18n/ca.ts index 21ad325607a6..fd062be7af4a 100644 --- a/packages/app/src/i18n/ca.ts +++ b/packages/app/src/i18n/ca.ts @@ -1170,4 +1170,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} sessions s'arxivaran.", "workspace.reset.note": "Això restablirà l'espai de treball perquè coincideixi amb la branca predeterminada.", "dialog.usageExceeded.dontShowAgain": "No ho tornis a mostrar", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/cs.ts b/packages/app/src/i18n/cs.ts index d79d4eb5a7f8..8f76f479a61e 100644 --- a/packages/app/src/i18n/cs.ts +++ b/packages/app/src/i18n/cs.ts @@ -1160,4 +1160,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} relací bude archivováno.", "workspace.reset.note": "Tím se pracovní prostor resetuje tak, aby odpovídal výchozí větvi.", "dialog.usageExceeded.dontShowAgain": "Znovu nezobrazovat", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/dv.ts b/packages/app/src/i18n/dv.ts index 27b5fea86999..12004c4c1a33 100644 --- a/packages/app/src/i18n/dv.ts +++ b/packages/app/src/i18n/dv.ts @@ -1174,4 +1174,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} ސެޝަންތައް އަރުޝީފް ކުރެވޭނެއެވެ.", "workspace.reset.note": "މިއީ ޑިފޯލްޓް ބްރާންޗާ އެއްގޮތްވާ ގޮތަށް ވޯކްސްޕޭސް ރީސެޓް ކުރާނެ ކަމެކެވެ.", "dialog.usageExceeded.dontShowAgain": "އަލުން ނުދައްކާ", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/dz.ts b/packages/app/src/i18n/dz.ts index 3b2ad46ca304..819c1b6299af 100644 --- a/packages/app/src/i18n/dz.ts +++ b/packages/app/src/i18n/dz.ts @@ -1178,4 +1178,8 @@ export const dict: Record = { "workspace.reset.archived.many": "{{count}} ལཱ་ཡུན་ཚུ་ཡིག་མཛོད་ནང་བཞག་འོང་།", "workspace.reset.note": "འདི་གིས་ སྔོན་སྒྲིག་ཡན་ལག་མཐུན་སྒྲིག་འབད་ནི་ལུ་ ལཱ་གི་ས་སྒོ་འདི་སླར་སྒྲིག་འབད་འོང་།", "dialog.usageExceeded.dontShowAgain": "ལོག་སྟེ་མ་སྟོན།", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/el.ts b/packages/app/src/i18n/el.ts index 8f91b46af010..f22af47f8d88 100644 --- a/packages/app/src/i18n/el.ts +++ b/packages/app/src/i18n/el.ts @@ -1170,4 +1170,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} θα αρχειοθετηθούν οι περίοδοι σύνδεσης.", "workspace.reset.note": "Αυτό θα επαναφέρει τον χώρο εργασίας ώστε να ταιριάζει με τον προεπιλεγμένο κλάδο.", "dialog.usageExceeded.dontShowAgain": "Να μην εμφανιστεί ξανά", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/et.ts b/packages/app/src/i18n/et.ts index d78296675e3d..6f7304a13603 100644 --- a/packages/app/src/i18n/et.ts +++ b/packages/app/src/i18n/et.ts @@ -1148,4 +1148,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} seanssi arhiivitakse.", "workspace.reset.note": "See lähtestab tööruumi, et see vastaks vaikeharule.", "dialog.usageExceeded.dontShowAgain": "Ära kuva enam", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/fa.ts b/packages/app/src/i18n/fa.ts index 0d3d721b5324..583e340cbb8b 100644 --- a/packages/app/src/i18n/fa.ts +++ b/packages/app/src/i18n/fa.ts @@ -1152,4 +1152,8 @@ export const dict = { "workspace.reset.archived.many": "جلسات {{count}} بایگانی خواهد شد.", "workspace.reset.note": "این کار فضای کاری را برای مطابقت با شاخه پیش فرض بازنشانی می کند.", "dialog.usageExceeded.dontShowAgain": "دیگر نشان نده", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/fi.ts b/packages/app/src/i18n/fi.ts index aa9c92574cff..877d10a4e88d 100644 --- a/packages/app/src/i18n/fi.ts +++ b/packages/app/src/i18n/fi.ts @@ -1165,4 +1165,8 @@ export const dict = { "help.tabs.persistence": "Kun avaat sovelluksen uudelleen, välilehtesi ovat yhä avoinna.", "help.tabs.worktrees": "Uusi ulkoasu ei vielä tue Git-työpuita, mutta tuki on tulossa pian. Jos haluat jatkaa aiemman ulkoasun käyttöä, voit vaihtaa ulkoasua asetuksissa. Huomaa kuitenkin, että uudesta ulkoasusta tulee pysyvä muutaman viikon kuluttua.", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/fo.ts b/packages/app/src/i18n/fo.ts index 4329a35b4247..f4e687a934f5 100644 --- a/packages/app/src/i18n/fo.ts +++ b/packages/app/src/i18n/fo.ts @@ -1151,4 +1151,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} setur verða arkiveraðar.", "workspace.reset.note": "Hetta nullstillar workspace til at passa til forsettu greinina.", "dialog.usageExceeded.dontShowAgain": "Vís ikki aftur", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/hi.ts b/packages/app/src/i18n/hi.ts index 0aaf8e14dc6d..feeea3a56b4e 100644 --- a/packages/app/src/i18n/hi.ts +++ b/packages/app/src/i18n/hi.ts @@ -1160,4 +1160,8 @@ export const dict = { "workspace.reset.archived.one": "1 सेशन संग्रहित किया जाएगा।", "workspace.reset.archived.many": "{{count}} सेशन संग्रहित किए जाएँगे।", "workspace.reset.note": "यह डिफ़ॉल्ट शाखा से मिलान करने के लिए वर्कस्पेस को रीसेट कर देगा।", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/hr.ts b/packages/app/src/i18n/hr.ts index 2130c88ada2d..40f359ecfe34 100644 --- a/packages/app/src/i18n/hr.ts +++ b/packages/app/src/i18n/hr.ts @@ -1165,4 +1165,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} sesije će biti arhivirane.", "workspace.reset.note": "Ovo će resetirati radni prostor kako bi odgovarao zadanoj grani.", "dialog.usageExceeded.dontShowAgain": "Ne prikazuj ponovno", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/hu.ts b/packages/app/src/i18n/hu.ts index 664abf8ee4c0..21b542128361 100644 --- a/packages/app/src/i18n/hu.ts +++ b/packages/app/src/i18n/hu.ts @@ -1167,4 +1167,8 @@ export const dict = { "workspace.reset.archived.many": "A {{count}} munkamenetek archiválva lesznek.", "workspace.reset.note": "Ezzel visszaállítja a munkaterületet, hogy megfeleljen az alapértelmezett ágnak.", "dialog.usageExceeded.dontShowAgain": "Ne jelenjen meg újra", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/hy.ts b/packages/app/src/i18n/hy.ts index 6eac43b5e6dd..af443c4ce7be 100644 --- a/packages/app/src/i18n/hy.ts +++ b/packages/app/src/i18n/hy.ts @@ -1162,4 +1162,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} նիստերը կարխիվացվեն։", "workspace.reset.note": "Սա կվերակայի աշխատանքային տարածքը, որպեսզի համապատասխանի լռելյայն ճյուղին:", "dialog.usageExceeded.dontShowAgain": "Այլևս չցուցադրել", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/id.ts b/packages/app/src/i18n/id.ts index 96dc3c7592e4..69d887afaa5e 100644 --- a/packages/app/src/i18n/id.ts +++ b/packages/app/src/i18n/id.ts @@ -1247,4 +1247,8 @@ export const dict = { "workspace.reset.archived.one": "1 sesi akan diarsipkan.", "workspace.reset.archived.many": "{{count}} sesi akan diarsipkan.", "workspace.reset.note": "Ini akan mengatur ulang ruang kerja agar cocok dengan cabang bawaan.", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/is.ts b/packages/app/src/i18n/is.ts index a45ed9a40cb0..c2b5c7e2cac5 100644 --- a/packages/app/src/i18n/is.ts +++ b/packages/app/src/i18n/is.ts @@ -1154,4 +1154,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} setur verða settar í geymslu.", "workspace.reset.note": "Þetta mun endurstilla vinnusvæðið til að passa við sjálfgefna útibúið.", "dialog.usageExceeded.dontShowAgain": "Ekki sýna aftur", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/it.ts b/packages/app/src/i18n/it.ts index a093e11485a3..952c27a4e556 100644 --- a/packages/app/src/i18n/it.ts +++ b/packages/app/src/i18n/it.ts @@ -1173,4 +1173,8 @@ export const dict = { "desktop.wsl.error.failedPort": "Impossibile ottenere la porta", "desktop.picker.error.notSelected": "Il file non è stato selezionato nella finestra di selezione", "desktop.picker.error.sizeLimit": "Gli allegati selezionati superano il limite di {{limit}} MB", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/ka.ts b/packages/app/src/i18n/ka.ts index be052930a2ec..6dcbd39fdc2f 100644 --- a/packages/app/src/i18n/ka.ts +++ b/packages/app/src/i18n/ka.ts @@ -1153,4 +1153,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} სესია დაარქივდება.", "workspace.reset.note": "ეს აღადგენს სამუშაო სივრცეს ნაგულისხმევი ფილიალის შესატყვისად.", "dialog.usageExceeded.dontShowAgain": "აღარ მაჩვენო", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/km.ts b/packages/app/src/i18n/km.ts index 7f1b61fe3667..bb2c5f029c58 100644 --- a/packages/app/src/i18n/km.ts +++ b/packages/app/src/i18n/km.ts @@ -1148,4 +1148,8 @@ export const dict = { "workspace.reset.archived.many": "សម័យ {{count}} នឹងត្រូវបានទុកក្នុងប័ណ្ណសារ។", "workspace.reset.note": "វានឹងកំណត់កន្លែងធ្វើការឡើងវិញដើម្បីផ្គូផ្គងសាខាលំនាំដើម។", "dialog.usageExceeded.dontShowAgain": "កុំបង្ហាញម្តងទៀត", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/lo.ts b/packages/app/src/i18n/lo.ts index acdc26df08aa..b7a1ec0d4fb6 100644 --- a/packages/app/src/i18n/lo.ts +++ b/packages/app/src/i18n/lo.ts @@ -1145,4 +1145,8 @@ export const dict = { "workspace.reset.archived.many": "ເຊດຊັນ {{count}} ຈະຖືກເກັບໄວ້.", "workspace.reset.note": "ນີ້ຈະຣີເຊັດພື້ນທີ່ເຮັດວຽກໃຫ້ກົງກັບສາຂາເລີ່ມຕົ້ນ.", "dialog.usageExceeded.dontShowAgain": "ຢ່າສະແດງອີກ", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/lt.ts b/packages/app/src/i18n/lt.ts index ba161eb31879..0609f8a0168d 100644 --- a/packages/app/src/i18n/lt.ts +++ b/packages/app/src/i18n/lt.ts @@ -1170,4 +1170,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} sesijos bus archyvuojamos.", "workspace.reset.note": "Tai iš naujo nustatys darbo sritį, kad ji atitiktų numatytąją šaką.", "dialog.usageExceeded.dontShowAgain": "Daugiau nerodyti", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/lv.ts b/packages/app/src/i18n/lv.ts index df4c5b2dcd91..b113413504d8 100644 --- a/packages/app/src/i18n/lv.ts +++ b/packages/app/src/i18n/lv.ts @@ -1159,4 +1159,8 @@ export const dict = { "workspace.reset.archived.many": "Tiks arhivētas {{count}} sesijas.", "workspace.reset.note": "Darbvieta tiks atiestatīta uz noklusējuma zara stāvokli.", "dialog.usageExceeded.dontShowAgain": "Vairs nerādīt", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/mk.ts b/packages/app/src/i18n/mk.ts index 01e60baa408b..c45cee57d609 100644 --- a/packages/app/src/i18n/mk.ts +++ b/packages/app/src/i18n/mk.ts @@ -1163,4 +1163,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} сесиите ќе бидат архивирани.", "workspace.reset.note": "Ова ќе го ресетира работниот простор за да одговара на стандардната гранка.", "dialog.usageExceeded.dontShowAgain": "Не прикажувај повторно", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/mn.ts b/packages/app/src/i18n/mn.ts index 41c4fa085d1a..17381788645e 100644 --- a/packages/app/src/i18n/mn.ts +++ b/packages/app/src/i18n/mn.ts @@ -1165,4 +1165,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} сессийг архивлах болно.", "workspace.reset.note": "Энэ нь ажлын талбарыг анхдагч салбартай тааруулахын тулд дахин тохируулах болно.", "dialog.usageExceeded.dontShowAgain": "Дахин бүү харуул", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/ms.ts b/packages/app/src/i18n/ms.ts index 2c3783d43b7d..1e3429381a4e 100644 --- a/packages/app/src/i18n/ms.ts +++ b/packages/app/src/i18n/ms.ts @@ -1154,4 +1154,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} sesi akan diarkibkan.", "workspace.reset.note": "Ini akan menetap semula ruang kerja agar sepadan dengan cawangan lalai.", "dialog.usageExceeded.dontShowAgain": "Jangan tunjukkan lagi", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/my.ts b/packages/app/src/i18n/my.ts index 6046f337ff06..7d281410efba 100644 --- a/packages/app/src/i18n/my.ts +++ b/packages/app/src/i18n/my.ts @@ -1173,4 +1173,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} ဆက်ရှင်များကို သိမ်းဆည်းထားပါမည်။", "workspace.reset.note": "၎င်းသည် မူရင်းဌာနခွဲနှင့် ကိုက်ညီစေရန် အလုပ်ခွင်ကို ပြန်လည်သတ်မှတ်ပါမည်။", "dialog.usageExceeded.dontShowAgain": "ထပ်မပြပါနှင့်", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/ne.ts b/packages/app/src/i18n/ne.ts index cc60bff9f400..f4071204ac0f 100644 --- a/packages/app/src/i18n/ne.ts +++ b/packages/app/src/i18n/ne.ts @@ -1154,4 +1154,8 @@ export const dict: Record = { "workspace.reset.archived.many": "{{count}} सत्रहरू अभिलेख गरिनेछ।", "workspace.reset.note": "यसले पूर्वनिर्धारित शाखासँग मिलाउन कार्यस्थान रिसेट गर्नेछ।", "dialog.usageExceeded.dontShowAgain": "फेरि नदेखाउनुहोस्", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/nl.ts b/packages/app/src/i18n/nl.ts index 6fb1dc09f24b..e2d0eee46ccc 100644 --- a/packages/app/src/i18n/nl.ts +++ b/packages/app/src/i18n/nl.ts @@ -1168,4 +1168,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} sessies worden gearchiveerd.", "workspace.reset.note": "Hierdoor wordt de werkruimte opnieuw ingesteld zodat deze overeenkomt met de standaardbranch.", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/pa.ts b/packages/app/src/i18n/pa.ts index 6298581c0aab..0585c3e26899 100644 --- a/packages/app/src/i18n/pa.ts +++ b/packages/app/src/i18n/pa.ts @@ -1158,4 +1158,8 @@ export const dict = { "workspace.reset.archived.one": "1 سیشن آرکائیو کیتا جائے گا۔", "workspace.reset.archived.many": "{{count}} سیشن آرکائیو کیتے جان گے۔", "workspace.reset.note": "ایہ ورک اسپیس نو ڈیفالٹ برانچ نال ملاون لئی ری سیٹ کرے گا۔", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/ro.ts b/packages/app/src/i18n/ro.ts index c84f9a4ee76e..d89100b0c5b4 100644 --- a/packages/app/src/i18n/ro.ts +++ b/packages/app/src/i18n/ro.ts @@ -1159,4 +1159,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} sesiuni vor fi arhivate.", "workspace.reset.note": "Aceasta va reseta spațiul de lucru la ramura implicită.", "dialog.usageExceeded.dontShowAgain": "Nu mai afișa", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/si.ts b/packages/app/src/i18n/si.ts index 00ca188b1581..72e5f9381c03 100644 --- a/packages/app/src/i18n/si.ts +++ b/packages/app/src/i18n/si.ts @@ -1150,4 +1150,8 @@ export const dict: Record = { "workspace.reset.archived.many": "සැසි {{count}} සංරක්ෂිත වනු ඇත.", "workspace.reset.note": "මෙය පෙරනිමි ශාඛාවට ගැලපෙන පරිදි වැඩබිම නැවත සකසනු ඇත.", "dialog.usageExceeded.dontShowAgain": "නැවත නොපෙන්වන්න", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/sk.ts b/packages/app/src/i18n/sk.ts index 9992e06cc4af..1b52d904e65e 100644 --- a/packages/app/src/i18n/sk.ts +++ b/packages/app/src/i18n/sk.ts @@ -1156,4 +1156,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} relácií bude archivovaných.", "workspace.reset.note": "Týmto sa pracovný priestor obnoví podľa predvolenej vetvy.", "dialog.usageExceeded.dontShowAgain": "Znova nezobrazovať", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/sl.ts b/packages/app/src/i18n/sl.ts index 3c23fcd7f708..f75a115591c3 100644 --- a/packages/app/src/i18n/sl.ts +++ b/packages/app/src/i18n/sl.ts @@ -1160,4 +1160,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} seje bodo arhivirane.", "workspace.reset.note": "To bo ponastavilo delovni prostor, da bo ustrezal privzeti veji.", "dialog.usageExceeded.dontShowAgain": "Ne prikaži več", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/sq.ts b/packages/app/src/i18n/sq.ts index ca94793ab0e6..023e7fceba28 100644 --- a/packages/app/src/i18n/sq.ts +++ b/packages/app/src/i18n/sq.ts @@ -1160,4 +1160,8 @@ export const dict = { "workspace.reset.archived.many": "Seancat {{count}} do të arkivohen.", "workspace.reset.note": "Kjo do të rivendosë hapësirën e punës që të përputhet me degën e paracaktuar.", "dialog.usageExceeded.dontShowAgain": "Mos e shfaq përsëri", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/sr.ts b/packages/app/src/i18n/sr.ts index f059160087d8..45bb475b5663 100644 --- a/packages/app/src/i18n/sr.ts +++ b/packages/app/src/i18n/sr.ts @@ -1158,4 +1158,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} сесије ће бити архивиране.", "workspace.reset.note": "Ово ће ресетовати радни простор тако да одговара подразумеваној грани.", "dialog.usageExceeded.dontShowAgain": "Не приказуј поново", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/sv.ts b/packages/app/src/i18n/sv.ts index 39b363c6b657..e2d48332a0b4 100644 --- a/packages/app/src/i18n/sv.ts +++ b/packages/app/src/i18n/sv.ts @@ -1157,4 +1157,8 @@ export const dict = { "workspace.reset.archived.one": "1 session kommer att arkiveras.", "workspace.reset.archived.many": "{{count}} sessioner kommer att arkiveras.", "workspace.reset.note": "Detta kommer att återställa arbetsytan så att den matchar standardgrenen.", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/tg.ts b/packages/app/src/i18n/tg.ts index ffa08409f684..93b1c9e127a9 100644 --- a/packages/app/src/i18n/tg.ts +++ b/packages/app/src/i18n/tg.ts @@ -1161,4 +1161,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} сессия архив карда мешаванд.", "workspace.reset.note": "Ин фазои кориро барои мувофиқ кардани филиали пешфарз барқарор мекунад.", "dialog.usageExceeded.dontShowAgain": "Дигар нишон надиҳед", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/tk.ts b/packages/app/src/i18n/tk.ts index 2b144c6a26c7..8950b6df4a70 100644 --- a/packages/app/src/i18n/tk.ts +++ b/packages/app/src/i18n/tk.ts @@ -1156,4 +1156,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} sessiýalary arhiwlener.", "workspace.reset.note": "Bu workspace-i adaty şaha gabat getirmek üçin täzeden düzer.", "dialog.usageExceeded.dontShowAgain": "Gaýtadan görkezme", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/ur.ts b/packages/app/src/i18n/ur.ts index 2ab69e7aec71..82a659218c4c 100644 --- a/packages/app/src/i18n/ur.ts +++ b/packages/app/src/i18n/ur.ts @@ -1161,4 +1161,8 @@ export const dict = { "workspace.reset.archived.one": "1 سیشن آرکائیو کیا جائے گا۔", "workspace.reset.archived.many": "{{count}} سیشنز آرکائیو کیے جائیں گے۔", "workspace.reset.note": "یہ ڈیفالٹ برانچ سے ملنے کے لیے ورک اسپیس کو دوبارہ ترتیب دے گا۔", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/uz.ts b/packages/app/src/i18n/uz.ts index 8cb7097fc07a..482093ac7136 100644 --- a/packages/app/src/i18n/uz.ts +++ b/packages/app/src/i18n/uz.ts @@ -1168,4 +1168,8 @@ export const dict = { "workspace.reset.archived.many": "{{count}} seanslari arxivlanadi.", "workspace.reset.note": "Bu standart filialga mos keladigan ish maydonini tiklaydi.", "dialog.usageExceeded.dontShowAgain": "Boshqa ko‘rsatma", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/app/src/i18n/vi.ts b/packages/app/src/i18n/vi.ts index 06fda4ce707c..34194bf8ef4a 100644 --- a/packages/app/src/i18n/vi.ts +++ b/packages/app/src/i18n/vi.ts @@ -1166,4 +1166,8 @@ export const dict = { "workspace.reset.archived.one": "1 phiên sẽ được lưu trữ.", "workspace.reset.archived.many": "{{count}} phiên sẽ được lưu trữ.", "workspace.reset.note": "Điều này sẽ thiết lập lại không gian làm việc để phù hợp với nhánh mặc định.", + "prompt.action.queue": "Queue", + "prompt.action.sendDirect": "Send direct", + "context.breakdown.toolDefs": "Tool Definitions", + "session.followupDock.editing": "Editing", } diff --git a/packages/opencode/src/provider/error.ts b/packages/opencode/src/provider/error.ts index 21149a2cf389..9ee15ae61814 100644 --- a/packages/opencode/src/provider/error.ts +++ b/packages/opencode/src/provider/error.ts @@ -1,6 +1,7 @@ import { APICallError } from "ai" import { STATUS_CODES } from "http" import { iife } from "@/util/iife" +import { isRecord } from "@/util/record" import type { ProviderV2 } from "@opencode-ai/core/provider" import { isContextOverflow } from "@opencode-ai/llm" @@ -70,17 +71,23 @@ function message(providerID: ProviderV2.ID, e: APICallError) { }).trim() } -function json(input: unknown) { +function json(input: unknown): Record | undefined { if (typeof input === "string") { try { const result = JSON.parse(input) - if (result && typeof result === "object") return result + if (isRecord(result)) return result return undefined } catch { return undefined } } - if (typeof input === "object" && input !== null) { + if (isRecord(input)) { + if (isRecord(input.reason) && typeof input.reason.raw === "string") { + return json(input.reason.raw) ?? input + } + if (isRecord(input.data) && typeof input.data.message === "string") { + return json(input.data.message) ?? input + } return input } return undefined @@ -105,9 +112,10 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined if (!body) return const responseBody = JSON.stringify(body) - if (body.type !== "error") return + if (body.type !== "error" && !isRecord(body.error)) return + const error = isRecord(body.error) ? body.error : {} - switch (body?.error?.code) { + switch (error.code) { case "context_length_exceeded": return { type: "context_overflow", @@ -131,7 +139,7 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined case "invalid_prompt": return { type: "api_error", - message: typeof body?.error?.message === "string" ? body?.error?.message : "Invalid prompt.", + message: typeof error.message === "string" ? error.message : "Invalid prompt.", isRetryable: false, responseBody, } @@ -139,7 +147,7 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined case "server_error": return { type: "api_error", - message: typeof body?.error?.message === "string" ? body?.error?.message : "Server error.", + message: typeof error.message === "string" ? error.message : "Server error.", isRetryable: true, responseBody, } @@ -165,7 +173,11 @@ export type ParsedAPICallError = export function parseAPICallError(input: { providerID: ProviderV2.ID; error: APICallError }): ParsedAPICallError { const m = message(input.providerID, input.error) const body = json(input.error.responseBody) - if (isContextOverflow(m) || input.error.statusCode === 413 || body?.error?.code === "context_length_exceeded") { + if ( + isContextOverflow(m) || + input.error.statusCode === 413 || + (body && isRecord(body.error) && body.error.code === "context_length_exceeded") + ) { return { type: "context_overflow", message: m, diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 1e9d75c7ec54..55f45d8193c6 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -711,6 +711,14 @@ export function fromError( e: unknown, ctx: { providerID: ProviderV2.ID; aborted?: boolean }, ): NonNullable { + const parsedStreamError = iife(() => { + try { + return ProviderError.parseStreamError(e) + } catch { + return undefined + } + }) + switch (true) { case e instanceof DOMException && e.name === "AbortError": return new AbortedError( @@ -806,33 +814,27 @@ export function fromError( }, { cause: e }, ).toObject() + case parsedStreamError !== undefined: + if (parsedStreamError.type === "context_overflow") { + return new ContextOverflowError( + { + message: parsedStreamError.message, + responseBody: parsedStreamError.responseBody, + }, + { cause: e }, + ).toObject() + } + return new APIError( + { + message: parsedStreamError.message, + isRetryable: parsedStreamError.isRetryable, + responseBody: parsedStreamError.responseBody, + }, + { cause: e }, + ).toObject() case e instanceof Error: return new NamedError.Unknown({ message: errorMessage(e) }, { cause: e }).toObject() default: - try { - const parsed = ProviderError.parseStreamError(e) - if (parsed) { - if (parsed.type === "context_overflow") { - return new ContextOverflowError( - { - message: parsed.message, - responseBody: parsed.responseBody, - }, - { cause: e }, - ).toObject() - } - return new APIError( - { - message: parsed.message, - isRetryable: parsed.isRetryable, - responseBody: parsed.responseBody, - }, - { - cause: e, - }, - ).toObject() - } - } catch {} return new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e }).toObject() } } diff --git a/packages/opencode/test/provider/error.test.ts b/packages/opencode/test/provider/error.test.ts new file mode 100644 index 000000000000..c79e9b905e56 --- /dev/null +++ b/packages/opencode/test/provider/error.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, test } from "bun:test" +import { InvalidProviderOutputReason, LLMError } from "@opencode-ai/llm" +import { ProviderError } from "@/provider/error" + +describe("ProviderError.parseStreamError", () => { + test("parses OpenAI-compatible direct stream error chunks", () => { + expect( + ProviderError.parseStreamError({ + error: { type: "server_error", code: "server_error", message: "temporarily unavailable" }, + }), + ).toEqual({ + type: "api_error", + message: "temporarily unavailable", + isRetryable: true, + responseBody: JSON.stringify({ + error: { type: "server_error", code: "server_error", message: "temporarily unavailable" }, + }), + }) + }) + + test("continues parsing wrapped stream error chunks", () => { + expect( + ProviderError.parseStreamError({ + type: "error", + error: { code: "server_error", message: "temporarily unavailable" }, + })?.type, + ).toBe("api_error") + }) + + test("parses raw chunks carried by an LLM stream error", () => { + const error = new LLMError({ + module: "ProviderShared", + method: "stream", + reason: new InvalidProviderOutputReason({ + route: "openai-compatible-chat", + message: "Invalid openai-compatible-chat stream event", + raw: JSON.stringify({ error: { code: "server_error", message: "temporarily unavailable" } }), + }), + }) + + expect(ProviderError.parseStreamError(error)).toEqual({ + type: "api_error", + message: "temporarily unavailable", + isRetryable: true, + responseBody: JSON.stringify({ error: { code: "server_error", message: "temporarily unavailable" } }), + }) + }) +}) diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index a622fbc926a4..95620ea491ab 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -10,6 +10,7 @@ import { SessionID, MessageID, PartID } from "../../src/session/schema" import { Question } from "../../src/question" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" +import { InvalidProviderOutputReason, LLMError } from "@opencode-ai/llm" const sessionID = SessionID.make("session") const providerID = ProviderV2.ID.make("test") @@ -1455,6 +1456,55 @@ describe("session.message-v2.fromError", () => { }) }) + test("serializes direct compatible stream errors as retryable APIError", () => { + const body = { + error: { + type: "server_error", + code: "server_error", + message: "temporarily unavailable", + }, + } + const result = MessageV2.fromError(body, { providerID }) + + expect(result).toStrictEqual({ + name: "APIError", + data: { + message: body.error.message, + isRetryable: true, + responseBody: JSON.stringify(body), + }, + }) + }) + + test("serializes compatible stream errors carried by LLMError as retryable APIError", () => { + const body = { + error: { + type: "server_error", + code: "server_error", + message: "temporarily unavailable", + }, + } + const error = new LLMError({ + module: "ProviderShared", + method: "stream", + reason: new InvalidProviderOutputReason({ + route: "openai-compatible-chat", + message: "Invalid openai-compatible-chat stream event", + raw: JSON.stringify(body), + }), + }) + const result = MessageV2.fromError(error, { 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/ui/src/context/marked-code-span.ts b/packages/ui/src/context/marked-code-span.ts new file mode 100644 index 000000000000..895935c3116d --- /dev/null +++ b/packages/ui/src/context/marked-code-span.ts @@ -0,0 +1,17 @@ +import type { MarkedExtension } from "marked" + +// Keep adjacent tilde and backtick runs separate until markedjs/marked#4011 is released. +export const markedCodeSpanBoundary = { + tokenizer: { + inlineText(src) { + const match = /^(`+(?=~)|~+(?=`))/.exec(src) + if (!match) return false + return { + type: "text", + raw: match[0], + text: match[0], + escaped: this.lexer.state.inRawBlock, + } + }, + }, +} satisfies MarkedExtension diff --git a/packages/ui/src/context/marked-parser.tsx b/packages/ui/src/context/marked-parser.tsx index 71e48351ca53..be54934b3b82 100644 --- a/packages/ui/src/context/marked-parser.tsx +++ b/packages/ui/src/context/marked-parser.tsx @@ -1,9 +1,11 @@ import katex from "katex" import { Marked, type MarkedExtension, type Tokens } from "marked" import markedShiki from "marked-shiki" +import { markedCodeSpanBoundary } from "./marked-code-span" export function createMarkdownParser(highlight: (code: string, language: string) => string | Promise) { return new Marked( + markedCodeSpanBoundary, { renderer: { link({ href, title, text }) { diff --git a/packages/ui/src/context/marked-regression.test.ts b/packages/ui/src/context/marked-regression.test.ts index 8861b930a073..ba67b2f67888 100644 --- a/packages/ui/src/context/marked-regression.test.ts +++ b/packages/ui/src/context/marked-regression.test.ts @@ -1,8 +1,9 @@ import { expect, test } from "bun:test" import { Marked } from "marked" +import { markedCodeSpanBoundary } from "./marked-code-span" test("preserves code spans adjacent to tildes", async () => { - const marked = new Marked() + const marked = new Marked(markedCodeSpanBoundary) expect(await marked.parse("~`0.1576` to measurement-window-only `0.00092`")).toBe( "

~0.1576 to measurement-window-only 0.00092

\n", From 707fbc5ad93e0f9e02acc7cf26f6b0980dede771 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 8 Aug 2026 23:32:37 -0300 Subject: [PATCH 009/129] fix(cli): await attached run output --- packages/opencode/src/cli/cmd/run.ts | 1 - .../opencode/test/cli/run/run-process.test.ts | 26 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index d8130c6ba6e7..b8436f233da4 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -930,7 +930,6 @@ export const RunCommand = effectCmd({ process.exitCode = 1 }) async function finish() { - if (args.attach) return const error = await completed if (error) process.exitCode = 1 } diff --git a/packages/opencode/test/cli/run/run-process.test.ts b/packages/opencode/test/cli/run/run-process.test.ts index bd5847e2723c..36a1f8a5051e 100644 --- a/packages/opencode/test/cli/run/run-process.test.ts +++ b/packages/opencode/test/cli/run/run-process.test.ts @@ -277,6 +277,32 @@ describe("opencode run (non-interactive subprocess)", () => { 60_000, ) + cliIt.live( + "attach mode waits for a delayed response before exiting", + ({ llm, opencode }) => + Effect.gen(function* () { + const gate = Promise.withResolvers() + yield* llm.hold("delayed attach response", gate.promise) + const server = yield* opencode.serve() + const run = yield* opencode.startRun("slow attach", { + format: "json", + extraArgs: ["--attach", server.url], + }) + + yield* llm.wait(1) + gate.resolve() + const result = yield* run.result + + opencode.expectExit(result, 0) + const events = opencode.parseJsonEvents(result.stdout) + expect(events.map((event) => event.type)).toEqual(["step_start", "text", "step_finish"]) + expect(events.find((event) => event.type === "text")?.part).toEqual( + expect.objectContaining({ type: "text", text: "delayed attach response" }), + ) + }), + 60_000, + ) + cliIt.live( "attach mode sends client-local file contents without a shared path", ({ home, llm, opencode }) => From 9e40a520688636e41a638292837fc334b0fdd8a4 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sun, 16 Aug 2026 22:27:03 -0300 Subject: [PATCH 010/129] fix(app): surface question reply errors and fall back to v1 SDK 404 bodies stringify as [object Object] in the question dock toast. Read the tagged message, copy answers before submit, and retry the legacy reply path when the v2 question is missing. --- .../composer/session-question-dock.tsx | 6 ++-- packages/app/src/utils/server-compat.test.ts | 28 ++++++++++++++++ packages/app/src/utils/server-compat.ts | 33 +++++++++++++++++-- packages/app/src/utils/server-errors.test.ts | 19 +++++++++++ packages/app/src/utils/server-errors.ts | 11 +++++++ 5 files changed, 92 insertions(+), 5 deletions(-) diff --git a/packages/app/src/pages/session/composer/session-question-dock.tsx b/packages/app/src/pages/session/composer/session-question-dock.tsx index 1fdcb11ccae6..684417d97402 100644 --- a/packages/app/src/pages/session/composer/session-question-dock.tsx +++ b/packages/app/src/pages/session/composer/session-question-dock.tsx @@ -6,6 +6,7 @@ import { DockPrompt } from "@opencode-ai/session-ui/dock-prompt" import { Icon } from "@opencode-ai/ui/icon" import { useSpring } from "@opencode-ai/ui/motion-spring" import { showToast } from "@/utils/toast" +import { formatServerError } from "@/utils/server-errors" import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2" import { useLanguage } from "@/context/language" import { useSDK } from "@/context/sdk" @@ -218,8 +219,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit }) const fail = (err: unknown) => { - const message = err instanceof Error ? err.message : String(err) - showToast({ title: language.t("common.requestFailed"), description: message }) + showToast({ title: language.t("common.requestFailed"), description: formatServerError(err, language.t) }) } const replyMutation = useMutation(() => ({ @@ -259,7 +259,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit await rejectMutation.mutateAsync() } - const submit = () => void reply(questions().map((_, i) => store.answers[i] ?? [])) + const submit = () => void reply(questions().map((_, i) => [...(store.answers[i] ?? [])])) const answered = (i: number) => { if ((store.answers[i]?.length ?? 0) > 0) return true diff --git a/packages/app/src/utils/server-compat.test.ts b/packages/app/src/utils/server-compat.test.ts index 46e57fa73d93..80d67455a621 100644 --- a/packages/app/src/utils/server-compat.test.ts +++ b/packages/app/src/utils/server-compat.test.ts @@ -7,6 +7,7 @@ function setup( responses?: { vcs?: { branch: string; default_branch: string } mcpResource?: { location: { directory: string; project: { id: string; directory: string } }; data: unknown } + currentQuestion?: "missing" }, ) { const requests: Request[] = [] @@ -43,6 +44,18 @@ function setup( if (request.method === "GET" && new URL(request.url).pathname === "/api/mcp/resource") return Response.json(responses?.mcpResource ?? []) if (request.method === "GET") return Response.json([]) + const pathname = new URL(request.url).pathname + if ( + request.method === "POST" && + responses?.currentQuestion === "missing" && + pathname.startsWith("/api/session/") && + pathname.includes("/question/") + ) { + return Response.json( + { _tag: "QuestionNotFoundError", requestID: "que_1", message: "Question request not found: que_1" }, + { status: 404 }, + ) + } return new Response(undefined, { status: 204 }) }, { preconnect: globalThis.fetch.preconnect }, @@ -199,6 +212,21 @@ describe("createCompatibleApi", () => { expect(url.searchParams.get("limit")).toBe("20") }) + test("falls back to the V1 question reply when the current request is missing", async () => { + const { api, requests } = setup("v2", { currentQuestion: "missing" }) + await api.question.reply({ + sessionID: "ses_1", + requestID: "que_1", + answers: [["Just disable rnnoise for now."]], + }) + + expect(requests.map((request) => new URL(request.url).pathname)).toEqual([ + "/api/session/ses_1/question/que_1/reply", + "/question/que_1/reply", + ]) + expect(await requests[1]!.json()).toEqual({ answers: [["Just disable rnnoise for now."]] }) + }) + test("routes V1 permission replies through the requested directory", async () => { const { api, requests } = setup("v1") await api.permission.reply({ diff --git a/packages/app/src/utils/server-compat.ts b/packages/app/src/utils/server-compat.ts index c2dd283e51b9..85cf7d11e953 100644 --- a/packages/app/src/utils/server-compat.ts +++ b/packages/app/src/utils/server-compat.ts @@ -85,16 +85,45 @@ function sessionInfo(session: Session): SessionInfo { export function createCompatibleApi(input: CompatibleInput): CompatibleApi { const v1 = createV1Api(input) - const current = createCurrentApi(input.current) + const current = createCurrentApi(input.current, input.legacy) return lazyApi( input.protocol.then((protocol) => (protocol === "v1" ? v1 : current)), current, ) } -function createCurrentApi(input: ServerApi): ServerApi { +function isMissingQuestion(error: unknown) { + const body = + error instanceof Error && error.cause && typeof error.cause === "object" && "body" in error.cause + ? (error.cause as Record).body + : error + if (!body || typeof body !== "object") return false + const tag = "name" in body && typeof body.name === "string" ? body.name : undefined + const labeled = "_tag" in body && typeof body._tag === "string" ? body._tag : tag + return labeled === "QuestionNotFoundError" || labeled === "SessionNotFoundError" +} + +function createCurrentApi(input: ServerApi, legacy: LegacyFor): ServerApi { return { ...input, + question: { + ...input.question, + reply(value: Parameters[0]) { + return input.question.reply(value).catch(async (error: unknown) => { + if (!isMissingQuestion(error)) throw error + await legacy().question.reply({ + requestID: value.requestID, + answers: value.answers.map((answer) => [...answer]), + }) + }) + }, + reject(value: Parameters[0]) { + return input.question.reject(value).catch(async (error: unknown) => { + if (!isMissingQuestion(error)) throw error + await legacy().question.reject({ requestID: value.requestID }) + }) + }, + }, project: { ...input.project, async list(...args) { diff --git a/packages/app/src/utils/server-errors.test.ts b/packages/app/src/utils/server-errors.test.ts index 9c735fe6891b..6ff453b36f7b 100644 --- a/packages/app/src/utils/server-errors.test.ts +++ b/packages/app/src/utils/server-errors.test.ts @@ -101,6 +101,25 @@ describe("formatServerError", () => { ) }) + test("extracts tagged JSON error bodies instead of [object Object]", () => { + expect( + formatServerError( + { + _tag: "QuestionNotFoundError", + requestID: "que_1", + message: "Question request not found: que_1", + }, + language.t, + ), + ).toBe("Question request not found: que_1") + expect( + formatServerError( + { name: "UnauthorizedError", data: { message: "Authentication required" } }, + language.t, + ), + ).toBe("Authentication required") + }) + test("formats provider model errors using provider/model", () => { const error = { name: "ProviderModelNotFoundError", diff --git a/packages/app/src/utils/server-errors.ts b/packages/app/src/utils/server-errors.ts index b34ae609ae2f..54a391b103c6 100644 --- a/packages/app/src/utils/server-errors.ts +++ b/packages/app/src/utils/server-errors.ts @@ -29,6 +29,8 @@ export function formatServerError(error: unknown, translate?: Translator, fallba const unwrapped = unwrapNamedError(error) if (isConfigInvalidErrorLike(unwrapped)) return parseReadableConfigInvalidError(unwrapped, translate) if (isProviderModelNotFoundErrorLike(unwrapped)) return parseReadableProviderModelNotFoundError(unwrapped, translate) + const named = namedErrorMessage(unwrapped) ?? namedErrorMessage(error) + if (named) return named if (error instanceof Error && error.message) return error.message if (typeof error === "string" && error) return error if (fallback) return fallback @@ -42,6 +44,15 @@ function unwrapNamedError(error: unknown): unknown { return error } +function namedErrorMessage(error: unknown) { + if (!error || typeof error !== "object") return + const value = error as Record + if (typeof value.message === "string" && value.message.trim()) return value.message + if (!value.data || typeof value.data !== "object") return + const message = (value.data as Record).message + if (typeof message === "string" && message.trim()) return message +} + // Client-synthesized session not-found errors share one constructor and // predicate so the message contract cannot drift between the sync store // (server-session.ts), the route lineage (session-lineage.ts), and the From 4578bbfe1a8736730dce715415719553018fb7ff Mon Sep 17 00:00:00 2001 From: henry701 Date: Sun, 16 Aug 2026 22:27:03 -0300 Subject: [PATCH 011/129] fix(opencode): share location map between HTTP replies and session drains QuestionV2 pending state lives on the SessionV2 LocationServiceMap. HTTP location middleware was resolving a second map from the app graph, so live question replies 404'd while the dock still showed the prompt. --- .../src/server/routes/instance/httpapi/server.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index b32a27cc5be2..eb1d8be1843d 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -274,6 +274,11 @@ export function createRoutes( corsOptions?: CorsOptions, ): Layer.Layer { const locationServiceMapV2 = buildLocationServiceMap() + // HTTP location middleware must use this map. `app` also provides LocationServiceMap + // (Agent/SystemPrompt embed locationServiceMapLayer), and that second map would hide + // in-memory QuestionV2/PermissionV2 state owned by SessionV2 drains. + const httpLocation = locationLayer.pipe(Layer.provide(locationServiceMapV2)) + const httpSessionLocation = sessionLocationLayer.pipe(Layer.provide(locationServiceMapV2)) return Layer.mergeAll( rootApiRoutes, @@ -294,8 +299,8 @@ export function createRoutes( HttpServer.layerServices, ]), Layer.provide(Layer.succeed(CorsConfig)(corsOptions)), - Layer.provide(sessionLocationLayer), - Layer.provide(locationLayer), + Layer.provide(httpSessionLocation), + Layer.provide(httpLocation), Layer.provide(PtyEnvironment.layer), Layer.provide( AppNodeBuilderV1.build(SessionV2.node, [ @@ -305,7 +310,12 @@ export function createRoutes( ), Layer.provide(locationServiceMapV2), - Layer.provide(AppNodeBuilderV1.build(app, [[SessionExecution.node, SessionExecutionLocal.node]])), + Layer.provide( + AppNodeBuilderV1.build(app, [ + [SessionExecution.node, SessionExecutionLocal.node], + [LocationServiceMap.node, locationServiceMapV2], + ]), + ), // Must stay last: layers provided later in this pipe build beneath earlier ones, // so Observability must come after every service graph. Otherwise eagerly forked // fibers (e.g. the ModelsDev background refresh) capture Effect's default stdout From fe8e6152abad76524ccd9748038750e46c64dddb Mon Sep 17 00:00:00 2001 From: henry701 Date: Mon, 24 Aug 2026 00:40:59 -0300 Subject: [PATCH 012/129] fix(app): repair V2 session composer state --- .../app/src/components/prompt-input-v2.tsx | 7 +- packages/app/src/components/prompt-input.tsx | 25 ++++- .../src/components/prompt-input/contracts.ts | 6 +- .../prompt-from-session-payload.test.ts | 24 ++++- .../prompt-from-session-payload.ts | 33 +++---- .../components/prompt-input/submit.test.ts | 88 ++++++++++++++++- .../app/src/components/prompt-input/submit.ts | 94 +++++++++++++------ .../src/components/session-context-usage.tsx | 8 +- packages/app/src/pages/session.tsx | 49 +++++++--- .../src/pages/session/current/model.test.ts | 39 ++++++++ .../app/src/pages/session/current/model.ts | 3 +- .../src/pages/session/current/reducer.test.ts | 74 +++++++++++++++ .../app/src/pages/session/current/reducer.ts | 92 +++++++++++++++++- .../session/timeline/message-timeline.tsx | 6 ++ .../pages/session/use-session-commands.tsx | 4 +- 15 files changed, 478 insertions(+), 74 deletions(-) diff --git a/packages/app/src/components/prompt-input-v2.tsx b/packages/app/src/components/prompt-input-v2.tsx index 812173d06e41..c356903e4682 100644 --- a/packages/app/src/components/prompt-input-v2.tsx +++ b/packages/app/src/components/prompt-input-v2.tsx @@ -112,7 +112,9 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps): }, []) }) const info = createMemo(() => (props.controls.session.id ? sync().session.get(props.controls.session.id) : undefined)) - const working = createMemo(() => sync().data.session_working(props.controls.session.id ?? "")) + const working = createMemo( + () => props.controls.session.working?.() ?? sync().data.session_working(props.controls.session.id ?? ""), + ) const attachments = createMemo(() => prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"), ) @@ -223,6 +225,9 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps): resetEditingQueueID: props.resetEditingQueueID, onQueue: props.onQueue, onAbort: props.onAbort, + revertMessageID: props.revertMessageID, + onRevertSubmit: props.onRevertSubmit, + onRevertSubmitComplete: props.onRevertSubmitComplete, onSubmit: props.onSubmit, model: props.controls.model.selection, }) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index caf40c314d40..0be2cf66f271 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -252,7 +252,9 @@ export const PromptInput: Component = (props) => { return paths }) const info = createMemo(() => (props.controls.session.id ? sync().session.get(props.controls.session.id) : undefined)) - const working = createMemo(() => sync().data.session_working(props.controls.session.id ?? "")) + const working = createMemo( + () => props.controls.session.working?.() ?? sync().data.session_working(props.controls.session.id ?? ""), + ) const imageAttachments = createMemo(() => prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"), ) @@ -1231,6 +1233,9 @@ export const PromptInput: Component = (props) => { resetEditingQueueID: props.resetEditingQueueID, onQueue: props.onQueue, onAbort: props.onAbort, + revertMessageID: props.revertMessageID, + onRevertSubmit: props.onRevertSubmit, + onRevertSubmitComplete: props.onRevertSubmitComplete, onSubmit: props.onSubmit, model: props.controls.model.selection, }) @@ -1401,7 +1406,13 @@ export const PromptInput: Component = (props) => { ) { return } - if (event.altKey && store.mode === "normal" && props.controls.session.id && props.onQueue && !props.editingQueueID?.()) { + if ( + event.altKey && + store.mode === "normal" && + props.controls.session.id && + props.onQueue && + !props.editingQueueID?.() + ) { queue(event) return } @@ -1590,7 +1601,11 @@ export const PromptInput: Component = (props) => { />
- + = (props) => { variant={queueMode() ? "secondary" : "ghost"} class="size-8" aria-pressed={queueMode()} - aria-label={queueMode() ? language.t("prompt.action.sendDirect") : language.t("prompt.action.queue")} + aria-label={ + queueMode() ? language.t("prompt.action.sendDirect") : language.t("prompt.action.queue") + } onClick={() => setQueueMode((value) => !value)} /> diff --git a/packages/app/src/components/prompt-input/contracts.ts b/packages/app/src/components/prompt-input/contracts.ts index b1d096e85698..f054972817f8 100644 --- a/packages/app/src/components/prompt-input/contracts.ts +++ b/packages/app/src/components/prompt-input/contracts.ts @@ -26,6 +26,7 @@ export type PromptInputControls = { } session: { id?: string + working?: () => boolean tabs: { active: () => string | undefined all: () => string[] @@ -55,6 +56,9 @@ export interface PromptInputProps { resetEditingQueueID?: () => void shouldQueue?: () => boolean onQueue?: (draft: FollowupDraft) => Promise | void - onAbort?: () => void + onAbort?: () => Promise | void + revertMessageID?: () => string | undefined + onRevertSubmit?: (messageID: string) => Promise | void + onRevertSubmitComplete?: () => void onSubmit?: () => void } diff --git a/packages/app/src/components/prompt-input/prompt-from-session-payload.test.ts b/packages/app/src/components/prompt-input/prompt-from-session-payload.test.ts index 0661efd52726..1b266ed594d0 100644 --- a/packages/app/src/components/prompt-input/prompt-from-session-payload.test.ts +++ b/packages/app/src/components/prompt-input/prompt-from-session-payload.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" import type { SessionInputPayload } from "@opencode-ai/schema/session-input-payload" -import { promptFromSessionPayload } from "./prompt-from-session-payload" +import { promptFromSessionMessage, promptFromSessionPayload } from "./prompt-from-session-payload" describe("promptFromSessionPayload", () => { test("restores native text, file, agent, and image parts without an SDK message adapter", () => { @@ -65,4 +65,26 @@ describe("promptFromSessionPayload", () => { expect(promptFromSessionPayload(payload)).toEqual([{ type: "text", content: "Visible", start: 0, end: 7 }]) }) + + test("prefers the durable user payload over stale normalized parts", () => { + const payload = { + version: 1, + agent: "reviewer", + model: { providerID: "openai", modelID: "gpt-5" }, + parts: [{ type: "text", text: "durable prompt" }], + } as unknown as SessionInputPayload.Payload + const message = { + id: "msg_1", + type: "user", + text: "durable prompt", + files: [], + agents: [], + payload, + time: { created: 1 }, + } as never + + expect(promptFromSessionMessage(message, [{ id: "stale", type: "text", text: "[attachment]" } as never])).toEqual([ + { type: "text", content: "durable prompt", start: 0, end: 14 }, + ]) + }) }) diff --git a/packages/app/src/components/prompt-input/prompt-from-session-payload.ts b/packages/app/src/components/prompt-input/prompt-from-session-payload.ts index 90be8654ec51..41dfe46a9b46 100644 --- a/packages/app/src/components/prompt-input/prompt-from-session-payload.ts +++ b/packages/app/src/components/prompt-input/prompt-from-session-payload.ts @@ -1,11 +1,9 @@ import type { SessionInputPayload } from "@opencode-ai/schema/session-input-payload" -import type { - AgentPart, - FileAttachmentPart, - ImageAttachmentPart, - Prompt, -} from "@/context/prompt" +import type { SessionMessage } from "@opencode-ai/schema/session-message" +import type { Part } from "@opencode-ai/sdk/v2" +import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt } from "@/context/prompt" import { createLegacyBlobReference } from "@/utils/draft-store" +import { extractPromptFromParts } from "@/utils/prompt" type Inline = | { @@ -25,16 +23,10 @@ type Inline = } type Payload = SessionInputPayload.Payload | SessionInputPayload.Encoded -export function promptFromSessionPayload( - payload: Payload, - options?: { directory?: string; attachmentName?: string }, -) { - const text = - payload.parts - .flatMap((part) => - part.type === "text" && part.synthetic !== true && part.ignored !== true ? [part.text] : [], - ) - .reduce((longest, part) => (part.length > longest.length ? part : longest), "") +export function promptFromSessionPayload(payload: Payload, options?: { directory?: string; attachmentName?: string }) { + const text = payload.parts + .flatMap((part) => (part.type === "text" && part.synthetic !== true && part.ignored !== true ? [part.text] : [])) + .reduce((longest, part) => (part.length > longest.length ? part : longest), "") const relative = (value: string) => { const directory = options?.directory if (!directory) return value @@ -135,3 +127,12 @@ export function promptFromSessionPayload( if (result.length === 0) result.push({ type: "text", content: "", start: 0, end: 0 }) return images.length === 0 ? result : [...result, ...images] } + +export function promptFromSessionMessage( + message: SessionMessage.Message | undefined, + parts: Part[], + options?: { directory?: string; attachmentName?: string }, +) { + if (message?.type === "user" && message.payload) return promptFromSessionPayload(message.payload, options) + return extractPromptFromParts(parts, options) +} diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts index 1250f1a1d41e..464bac62ae8c 100644 --- a/packages/app/src/components/prompt-input/submit.test.ts +++ b/packages/app/src/components/prompt-input/submit.test.ts @@ -39,6 +39,8 @@ const commandCalls: unknown[] = [] const currentCommands: Array<{ name: string; template: string; description?: string }> = [] const promotedDrafts: Array<{ draftID: string; server: string; sessionId: string }> = [] const resumedQueues: string[] = [] +const abortOrder: string[] = [] +const todoCleared: string[] = [] let params: { id?: string } = {} let search: { draftId?: string } = {} @@ -241,7 +243,9 @@ beforeAll(async () => { promptAsyncCalls.push(input) return { id: "input-1" } }, - interrupt: async () => undefined, + interrupt: async () => { + abortOrder.push("interrupt") + }, shell: async (input: { sessionID: string }) => { sentShell.push(sessionLocations[input.sessionID] ?? input.sessionID) }, @@ -277,7 +281,9 @@ beforeAll(async () => { remove: () => undefined, }, }, - set: () => undefined, + set: (...args: unknown[]) => { + if (args[0] === "todo" && typeof args[1] === "string") todoCleared.push(args[1]) + }, }), })) @@ -343,6 +349,8 @@ beforeEach(() => { commandCalls.length = 0 currentCommands.length = 0 resumedQueues.length = 0 + abortOrder.length = 0 + todoCleared.length = 0 selected = "/repo/worktree-a" variant = undefined permissionServer = "server-a" @@ -641,6 +649,82 @@ describe("prompt submit worktree selection", () => { }) describe("prompt submit queue mode", () => { + test("replaces a staged rollback instead of queueing or repeating it", async () => { + params = { id: "session-1" } + const order: string[] = [] + const submit = createPromptSubmit({ + prompt, + info: () => ({ id: "session-1" }), + imageAttachments: () => [], + commentCount: () => 0, + autoAccept: () => false, + mode: () => "normal", + working: () => false, + editor: () => undefined, + queueScroll: () => undefined, + promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0), + addToHistory: () => undefined, + resetHistoryNavigation: () => undefined, + setMode: () => undefined, + setPopover: () => undefined, + queueMode: () => true, + revertMessageID: () => "message-1", + onRevertSubmit: async (messageID) => { + order.push(`stage:${messageID}`) + }, + onRevertSubmitComplete: () => order.push("complete"), + onSubmit: () => order.push("submit"), + }) + + await submit.handleSubmit({ preventDefault: () => undefined } as unknown as Event) + await Promise.resolve() + + expect(order).toEqual(["stage:message-1", "submit"]) + expect(promptAsyncCalls).toHaveLength(1) + expect(resumedQueues).toEqual(["session-1"]) + expect(queuedDrafts).toHaveLength(0) + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(order).toEqual(["stage:message-1", "submit", "complete"]) + }) + + test("pauses queue draining before interrupting and preserves admitted input", async () => { + params = { id: "session-1" } + let release = () => {} + const gate = new Promise((resolve) => { + release = resolve + }) + const submit = createPromptSubmit({ + prompt, + info: () => ({ id: "session-1" }), + imageAttachments: () => [], + commentCount: () => 0, + autoAccept: () => false, + mode: () => "normal", + working: () => true, + editor: () => undefined, + queueScroll: () => undefined, + promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0), + addToHistory: () => undefined, + resetHistoryNavigation: () => undefined, + setMode: () => undefined, + setPopover: () => undefined, + onAbort: async () => { + await gate + abortOrder.push("pause") + }, + }) + + const pending = submit.abort() + await Promise.resolve() + expect(abortOrder).toEqual([]) + release() + await pending + + expect(abortOrder).toEqual(["pause", "interrupt"]) + expect(todoCleared).toEqual([]) + expect(promptAsyncCalls).toHaveLength(0) + }) + test("recognizes queued slash commands from the current catalog before routing the draft", async () => { params = { id: "session-1" } promptValue = [{ type: "text", content: "/review now", start: 0, end: 11 }] diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index 4aeaf40de0ef..6c05a149ad47 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -91,7 +91,10 @@ type PromptSubmitInput = { editingQueuePayload?: Accessor resetEditingQueueID?: () => void onQueue?: (draft: FollowupDraft) => Promise | void - onAbort?: () => void + onAbort?: () => Promise | void + revertMessageID?: Accessor + onRevertSubmit?: (messageID: string) => Promise | void + onRevertSubmitComplete?: () => void onSubmit?: () => void model?: ModelSelection } @@ -125,9 +128,12 @@ export function createPromptSubmit(input: PromptSubmitInput) { const sessionID = params.id if (!sessionID) return Promise.resolve() - serverSync().session.set("todo", sessionID, []) - - input.onAbort?.() + await Promise.resolve(input.onAbort?.()).catch((err) => { + showToast({ + title: language.t("common.requestFailed"), + description: errorMessage(err), + }) + }) const key = pendingKey(sessionID) const queued = pending.get(key) @@ -137,7 +143,14 @@ export function createPromptSubmit(input: PromptSubmitInput) { pending.delete(key) return Promise.resolve() } - return serverSDK().currentClient.sessions.interrupt({ sessionID }).catch(() => {}) + return serverSDK() + .currentClient.sessions.interrupt({ sessionID }) + .catch((err) => { + showToast({ + title: language.t("common.requestFailed"), + description: errorMessage(err), + }) + }) } const restoreCommentItems = ( @@ -259,8 +272,8 @@ export function createPromptSubmit(input: PromptSubmitInput) { let session = input.info() ?? (params.id ? { id: params.id } : undefined) if (!session && isNewSession) { - const created = await serverSDK().currentClient.sessions - .create({ + const created = await serverSDK() + .currentClient.sessions.create({ agent, model: { id: model.modelID, providerID: model.providerID, variant }, location: { directory: sessionDirectory }, @@ -302,8 +315,8 @@ export function createPromptSubmit(input: PromptSubmitInput) { const commandInput = mode === "normal" ? text.match(/^\/(\S+)(?:[ \t]+([\s\S]*))?$/) : undefined const commandName = commandInput?.[1] const commands = commandName - ? await serverSDK().currentClient.commands - .list({ location: { directory: sessionDirectory } }) + ? await serverSDK() + .currentClient.commands.list({ location: { directory: sessionDirectory } }) .then((result) => result.data) .catch((err) => { showToast({ @@ -325,9 +338,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { variant, queueID: input.editingQueueID?.(), queuePayload: input.editingQueuePayload?.(), - ...(customCommand && commandName - ? { command: { name: commandName, arguments: commandInput?.[2] ?? "" } } - : {}), + ...(customCommand && commandName ? { command: { name: commandName, arguments: commandInput?.[2] ?? "" } } : {}), } const clearInput = () => { @@ -353,7 +364,26 @@ export function createPromptSubmit(input: PromptSubmitInput) { return true } - if (!isNewSession && mode === "normal" && (draft.queueID || input.shouldQueue?.() || queueMode)) { + const revertMessageID = input.revertMessageID?.() + if (!isNewSession && mode === "normal" && revertMessageID) { + const reverted = await Promise.resolve(input.onRevertSubmit?.(revertMessageID)) + .then(() => true) + .catch((err) => { + showToast({ + title: language.t("common.requestFailed"), + description: errorMessage(err), + }) + return false + }) + if (!reverted) return + } + + if ( + !revertMessageID && + !isNewSession && + mode === "normal" && + (draft.queueID || input.shouldQueue?.() || queueMode) + ) { const saved = await Promise.resolve(input.onQueue?.(draft)) .then(() => true) .catch((err) => { @@ -373,14 +403,16 @@ export function createPromptSubmit(input: PromptSubmitInput) { input.resetQueueMode?.() - void serverSDK().currentClient.sessions.queueDrainResume({ sessionID: session.id }).catch(() => {}) + void serverSDK() + .currentClient.sessions.queueDrainResume({ sessionID: session.id }) + .catch(() => {}) input.onSubmit?.() if (mode === "shell") { clearInput() - serverSDK().currentClient.sessions - .shell({ sessionID: session.id, command: text }) + serverSDK() + .currentClient.sessions.shell({ sessionID: session.id, command: text }) .catch((err) => { showToast({ title: language.t("prompt.toast.shellSendFailed.title"), @@ -396,8 +428,8 @@ export function createPromptSubmit(input: PromptSubmitInput) { const payload = draft.prompt.some((part) => part.type === "image") ? await createSessionPayloadWithImages(draft) : createSessionPayload(draft) - serverSDK().currentClient.sessions - .command({ + serverSDK() + .currentClient.sessions.command({ id: Identifier.ascending("message"), sessionID: session.id, name: draft.command.name, @@ -492,18 +524,22 @@ export function createPromptSubmit(input: PromptSubmitInput) { draft, messageID, before: waitForWorktree, - }).catch((err) => { - pending.delete(pendingKey(session.id)) - if (sessionDirectory === projectDirectory) { - sync().set("session_status", session.id, { type: "idle" }) - } - showToast({ - title: language.t("prompt.toast.promptSendFailed.title"), - description: errorMessage(err), - }) - removeOptimisticMessage() - if (restoreInput()) restoreCommentItems(submission.target(), commentItems) }) + .then((sent) => { + if (sent) input.onRevertSubmitComplete?.() + }) + .catch((err) => { + pending.delete(pendingKey(session.id)) + if (sessionDirectory === projectDirectory) { + sync().set("session_status", session.id, { type: "idle" }) + } + showToast({ + title: language.t("prompt.toast.promptSendFailed.title"), + description: errorMessage(err), + }) + removeOptimisticMessage() + if (restoreInput()) restoreCommentItems(submission.target(), commentItems) + }) } return { diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index 5ff332c86dbd..b2b446234372 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -22,8 +22,8 @@ interface SessionContextUsageProps { variant?: "button" | "indicator" buttonAppearance?: "default" | "v2" placement?: ComponentProps["placement"] - messages?: () => readonly SessionMessage.Message[] - session?: () => Session.Info | undefined + messages: () => readonly SessionMessage.Message[] + session: () => Session.Info | undefined } function ContextTooltipRow(props: { name: JSX.Element; value: JSX.Element }) { @@ -72,9 +72,9 @@ export function SessionContextUsage(props: SessionContextUsageProps) { }), ) - const context = createMemo(() => getSessionContext(props.messages?.() ?? [], [...providers.all().values()])) + const context = createMemo(() => getSessionContext(props.messages(), [...providers.all().values()])) const cost = createMemo(() => { - return usd().format(props.session?.()?.cost ?? 0) + return usd().format(props.session()?.cost ?? 0) }) const contextVisible = createMemo(() => view().reviewPanel.opened() && tabState.activeTab() === "context") const hasOtherTabs = createMemo(() => diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index dc5bcb51a0cb..3ea95a64ab60 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -64,7 +64,7 @@ import { useSettingsCommand } from "@/components/settings-dialog" import { setCursorPosition } from "@/components/prompt-input/editor-dom" import { promptLength } from "@/components/prompt-input/history" import { type FollowupDraft } from "@/components/prompt-input/submit" -import { promptFromSessionPayload } from "@/components/prompt-input/prompt-from-session-payload" +import { promptFromSessionMessage } from "@/components/prompt-input/prompt-from-session-payload" import { createPromptInputController, createSessionComposerController, @@ -97,7 +97,6 @@ import { useSessionCommands } from "@/pages/session/use-session-commands" import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll" import { queuedFollowup, saveQueuedFollowup } from "@/pages/session/session-queue" import { diffs as list } from "@/utils/diffs" -import { extractPromptFromParts } from "@/utils/prompt" import { formatServerError, isLocalSessionNotFoundError, isSessionNotFoundError } from "@/utils/server-errors" import { legacySessionHref, requireServerKey, sessionHref } from "@/utils/session-route" import { useUsageExceededDialogs } from "./session/usage-exceeded-dialogs" @@ -1172,6 +1171,7 @@ export default function Page() { navigateMessageByOffset, setActiveMessage, focusInput, + getMessageParts: timelineParts, review: reviewTab, fileBrowser: () => newSessionDesign() && isDesktop() && !!params.id, }) @@ -1699,11 +1699,14 @@ export default function Page() { ), ) - const draft = (id: string) => - extractPromptFromParts(sync().data.part[id] ?? [], { + const draft = (id: string) => { + const message = current.messages().find((item) => item.id === id) + const parts = timelineParts(id) + return promptFromSessionMessage(message, parts.length > 0 ? parts : (sync().data.part[id] ?? []), { directory: sdk().directory, attachmentName: language.t("common.attachment"), }) + } const line = (id: string) => { const text = draft(id) @@ -1738,10 +1741,14 @@ export default function Page() { })), ) const [editingFollowup, setEditingFollowup] = createSignal() + const [editingRevert, setEditingRevert] = createSignal() createEffect( on( () => params.id, - () => setEditingFollowup(undefined), + () => { + setEditingFollowup(undefined) + setEditingRevert(undefined) + }, { defer: true }, ), ) @@ -1872,7 +1879,7 @@ export default function Page() { prompt.set(value) }, request: () => halt(input.sessionID).then(() => session.revert.stage(input)), - complete: () => undefined, + complete: () => setEditingRevert(input.messageID), rollback: () => roll(input.sessionID, last, target), fail, }) @@ -1905,7 +1912,7 @@ export default function Page() { !next ? halt(sessionID).then(() => session.revert.clear({ sessionID })) : halt(sessionID).then(() => session.revert.stage({ sessionID, messageID: next.id }).then(() => undefined)), - complete: () => undefined, + complete: () => setEditingRevert(next?.id), rollback: () => roll(sessionID, last, target), fail, }) @@ -2120,6 +2127,8 @@ export default function Page() { centered={centered()} messages={messages} sessionMessages={timelineSessionMessages} + contextMessages={contextMessages} + contextSession={current.session} getMessageParts={timelineParts} setContentRef={(el) => { content = el @@ -2230,12 +2239,21 @@ export default function Page() { editingQueueID={() => editingFollowup()?.queueID} editingQueuePayload={() => editingFollowup()?.queuePayload} resetEditingQueueID={clearFollowupEdit} + revertMessageID={() => editingRevert() ?? revertMessageID()} + onRevertSubmit={(messageID) => { + const id = params.id + if (!id) return Promise.resolve() + return sdk() + .api.session.revert.stage({ sessionID: id, messageID }) + .then(() => undefined) + }} + onRevertSubmitComplete={() => setEditingRevert(undefined)} shouldQueue={queueEnabled} onQueue={queueFollowup} onAbort={() => { const id = params.id - if (!id) return - void serverSDK().currentClient.sessions.queueDrainPause({ sessionID: id }) + if (!id) return Promise.resolve() + return serverSDK().currentClient.sessions.queueDrainPause({ sessionID: id }) }} /> } @@ -2264,12 +2282,21 @@ export default function Page() { editingQueueID: () => editingFollowup()?.queueID, editingQueuePayload: () => editingFollowup()?.queuePayload, resetEditingQueueID: clearFollowupEdit, + revertMessageID: () => editingRevert() ?? revertMessageID(), + onRevertSubmit: (messageID) => { + const id = params.id + if (!id) return Promise.resolve() + return sdk() + .api.session.revert.stage({ sessionID: id, messageID }) + .then(() => undefined) + }, + onRevertSubmitComplete: () => setEditingRevert(undefined), shouldQueue: queueEnabled, onQueue: queueFollowup, onAbort: () => { const id = params.id - if (!id) return - void serverSDK().currentClient.sessions.queueDrainPause({ sessionID: id }) + if (!id) return Promise.resolve() + return serverSDK().currentClient.sessions.queueDrainPause({ sessionID: id }) }, }) return diff --git a/packages/app/src/pages/session/current/model.test.ts b/packages/app/src/pages/session/current/model.test.ts index 585210b95d8f..4844eaf73c7d 100644 --- a/packages/app/src/pages/session/current/model.test.ts +++ b/packages/app/src/pages/session/current/model.test.ts @@ -27,6 +27,20 @@ const prompted = (seq: number) => }, }) as const +const admitted = (seq: number) => + ({ + id: `evt_${seq}`, + type: "session.next.prompt.admitted", + durable: { aggregateID: "ses_test", seq, version: 1 }, + data: { + timestamp: seq, + sessionID: "ses_test", + messageID: "msg_steer", + prompt: { text: "steer now" }, + delivery: "steer", + }, + }) as const + const messages = ( items: Array<{ id: string; text: string; created: number }>, next?: string, @@ -105,6 +119,31 @@ describe("current session model", () => { }) }) + test("shows admitted steering input and reports the session busy before promotion", async () => { + const port = makePort({ events: admitted(2), pages: [messages([], undefined, 1)] }) + + await new Promise((resolve, reject) => { + createRoot((dispose) => { + const model = createCurrentSessionModel({ + sessionID: () => "ses_test", + client: () => port, + autoStart: false, + }) + model + .start() + .then(() => { + expect(model.messages().map((message) => String(message.id))).toEqual(["msg_steer"]) + expect(model.messages()[0]).toMatchObject({ type: "user", text: "steer now" }) + expect(model.busy()).toBe(true) + model.dispose() + dispose() + resolve() + }) + .catch(reject) + }) + }) + }) + test("loads older pages in chronological order", async () => { const port = makePort({ pages: [ diff --git a/packages/app/src/pages/session/current/model.ts b/packages/app/src/pages/session/current/model.ts index 696b96b4c633..b6c0a5d7ee24 100644 --- a/packages/app/src/pages/session/current/model.ts +++ b/packages/app/src/pages/session/current/model.ts @@ -8,6 +8,7 @@ import { Schema } from "effect" import { createEffect, createSignal, on, onCleanup, type Accessor } from "solid-js" import { currentSessionInitialState, + currentSessionMessages, reduceCurrentSession, type CurrentSessionAction, type CurrentSessionState, @@ -312,7 +313,7 @@ export function createCurrentSessionModel(input: { return { state, - messages: () => state().messages, + messages: () => currentSessionMessages(state()), context: () => state().context, queue: () => state().queue, session: () => state().session, diff --git a/packages/app/src/pages/session/current/reducer.test.ts b/packages/app/src/pages/session/current/reducer.test.ts index e9a0b2f3a072..99841a4df1e7 100644 --- a/packages/app/src/pages/session/current/reducer.test.ts +++ b/packages/app/src/pages/session/current/reducer.test.ts @@ -302,4 +302,78 @@ describe("current session reducer", () => { expect(retrying.active).toBe(true) expect(running.retry).toBeUndefined() }) + + test("projects admitted steering prompts until the durable prompt is promoted", () => { + const admitted = decodeEvent({ + id: "evt_admitted", + type: "session.next.prompt.admitted", + durable: { aggregateID: "ses_test", seq: 2, version: 1 }, + data: { + timestamp: 2, + sessionID: "ses_test", + messageID: "msg_steer", + prompt: { text: "steer now" }, + delivery: "steer", + }, + }) + const prompted = decodeEvent({ + id: "evt_prompted", + type: "session.next.prompted", + durable: { aggregateID: "ses_test", seq: 3, version: 1 }, + data: { + timestamp: 3, + sessionID: "ses_test", + messageID: "msg_steer", + prompt: { text: "steer now" }, + delivery: "steer", + }, + }) + + const pending = dispatch([ + { type: "hydrated", sequence: 1, messages: [] }, + { type: "event", event: admitted }, + ]) + + expect(pending.pending.map((message) => message.text)).toEqual(["steer now"]) + expect(pending.active).toBe(true) + + const promoted = reduceCurrentSession(pending, { type: "event", event: prompted }) + expect(promoted.pending).toEqual([]) + expect(promoted.messages).toMatchObject([{ id: "msg_steer", type: "user", text: "steer now" }]) + }) + + test("keeps an admitted steering prompt across interruption until it is discarded or promoted", () => { + const admitted = decodeEvent({ + id: "evt_admitted", + type: "session.next.prompt.admitted", + durable: { aggregateID: "ses_test", seq: 2, version: 1 }, + data: { + timestamp: 2, + sessionID: "ses_test", + messageID: "msg_steer", + prompt: { text: "preserve me" }, + delivery: "steer", + }, + }) + const interrupted = decodeEvent({ + id: "evt_failed", + type: "session.next.step.failed", + durable: { aggregateID: "ses_test", seq: 3, version: 1 }, + data: { + timestamp: 3, + sessionID: "ses_test", + assistantMessageID: "msg_assistant", + error: { type: "interrupted", message: "Provider turn interrupted" }, + }, + }) + + const state = dispatch([ + { type: "hydrated", sequence: 1, messages: [] }, + { type: "event", event: admitted }, + { type: "event", event: interrupted }, + ]) + + expect(state.pending.map((message) => message.text)).toEqual(["preserve me"]) + expect(state.active).toBe(false) + }) }) diff --git a/packages/app/src/pages/session/current/reducer.ts b/packages/app/src/pages/session/current/reducer.ts index 4b0f45be7adc..fb09e056ea16 100644 --- a/packages/app/src/pages/session/current/reducer.ts +++ b/packages/app/src/pages/session/current/reducer.ts @@ -1,5 +1,5 @@ import type { SessionEvent } from "@opencode-ai/schema/session-event" -import type { SessionMessage } from "@opencode-ai/schema/session-message" +import { SessionMessage } from "@opencode-ai/schema/session-message" import type { Session } from "@opencode-ai/schema/session" import type { SessionInput } from "@opencode-ai/schema/session-input" import { SessionMessageUpdater } from "@opencode-ai/core/session/message-updater" @@ -7,6 +7,7 @@ import { DateTime, Effect } from "effect" export type CurrentSessionState = { readonly messages: ReadonlyArray + readonly pending: ReadonlyArray readonly context: ReadonlyArray readonly queue: ReadonlyArray readonly session?: Session.Info @@ -53,6 +54,7 @@ export type CurrentSessionAction = export function currentSessionInitialState(): CurrentSessionState { return { messages: [], + pending: [], context: [], queue: [], active: false, @@ -71,6 +73,7 @@ export function reduceCurrentSession(state: CurrentSessionState, action: Current return { ...state, messages: [...action.messages], + pending: reconcilePending(state.pending, action.messages), readiness: "ready" as const, cursor: action.cursor, hasOlder: action.cursor !== undefined, @@ -83,6 +86,7 @@ export function reduceCurrentSession(state: CurrentSessionState, action: Current return { ...state, messages: [...action.messages.filter((message) => !loaded.has(message.id)), ...state.messages], + pending: reconcilePending(state.pending, action.messages), cursor: action.cursor, hasOlder: action.cursor !== undefined, } @@ -100,6 +104,7 @@ export function reduceCurrentSession(state: CurrentSessionState, action: Current return { ...state, messages: Array.from(messages.values()).toSorted((left, right) => order(left).localeCompare(order(right))), + pending: reconcilePending(state.pending, action.messages), } } case "message-replaced": { @@ -112,16 +117,18 @@ export function reduceCurrentSession(state: CurrentSessionState, action: Current DateTime.toEpochMillis(left.time.created) - DateTime.toEpochMillis(right.time.created) || String(left.id).localeCompare(String(right.id)), ), + pending: reconcilePending(state.pending, [action.message]), } const messages = [...state.messages] messages[index] = action.message - return { ...state, messages } + return { ...state, messages, pending: reconcilePending(state.pending, [action.message]) } } case "event": { const sequence = action.event.durable?.seq if (sequence !== undefined && sequence <= (state.lastEventSequence ?? -1)) return state const messages = [...state.messages] Effect.runSync(SessionMessageUpdater.update(SessionMessageUpdater.memory({ messages }), action.event)) + const pending = reducePending(state.pending, action.event, messages) const retry = action.event.type === "session.next.retried" ? action.event @@ -133,7 +140,8 @@ export function reduceCurrentSession(state: CurrentSessionState, action: Current return { ...state, messages, - active: action.event.type === "session.next.retried" ? true : state.active, + pending, + active: activeForEvent(state.active, action.event), retry, lastEventSequence: sequence ?? state.lastEventSequence, } @@ -156,6 +164,84 @@ export function reduceCurrentSession(state: CurrentSessionState, action: Current } } +function activeForEvent(active: boolean, event: SessionEvent.Event) { + if (event.type === "session.next.retried") return true + if (event.type === "session.next.prompt.admitted" && event.data.delivery === "steer") return true + if (event.type === "session.next.prompt.expedited") return true + if (event.type === "session.next.step.started") return true + if (event.type === "session.next.step.ended" || event.type === "session.next.step.failed") return false + return active +} + +export function currentSessionMessages(state: CurrentSessionState) { + if (state.pending.length === 0) return state.messages + return [...state.messages, ...state.pending].toSorted((left, right) => order(left).localeCompare(order(right))) +} + +function reconcilePending(pending: readonly SessionMessage.User[], messages: readonly SessionMessage.Message[]) { + if (pending.length === 0) return pending + const promoted = new Set(messages.map((message) => message.id)) + return pending.filter((message) => !promoted.has(message.id)) +} + +function reducePending( + pending: readonly SessionMessage.User[], + event: SessionEvent.Event, + messages: readonly SessionMessage.Message[], +) { + const reconciled = reconcilePending(pending, messages) + if (event.type === "session.next.prompt.admitted" && event.data.delivery === "steer") + return upsertPending(reconciled, pendingUser(event.data, event.metadata)) + if (event.type === "session.next.prompt.expedited") + return upsertPending(reconciled, pendingUser(event.data, event.metadata)) + if (event.type === "session.next.prompt.revised") { + const current = reconciled.find((message) => message.id === event.data.messageID) + if (!current) return reconciled + return upsertPending( + reconciled, + pendingUser( + { + messageID: event.data.messageID, + prompt: event.data.prompt, + payload: event.data.payload, + timestamp: event.data.timestamp, + }, + current.metadata, + ), + ) + } + if (event.type === "session.next.prompt.discarded") + return reconciled.filter((message) => message.id !== event.data.messageID) + if (event.type === "session.next.prompted") return reconciled.filter((message) => message.id !== event.data.messageID) + return reconciled +} + +function upsertPending(pending: readonly SessionMessage.User[], message: SessionMessage.User) { + const next = pending.filter((current) => current.id !== message.id) + return [...next, message].toSorted((left, right) => order(left).localeCompare(order(right))) +} + +function pendingUser( + data: { + messageID: SessionMessage.ID + prompt: SessionEvent.PromptAdmitted["data"]["prompt"] + payload?: SessionEvent.PromptAdmitted["data"]["payload"] + timestamp: SessionEvent.PromptAdmitted["data"]["timestamp"] + }, + metadata?: Record, +) { + return SessionMessage.User.make({ + id: data.messageID, + type: "user", + metadata, + text: data.prompt.text, + files: data.prompt.files, + agents: data.prompt.agents, + payload: data.payload, + time: { created: data.timestamp }, + }) +} + function order(message: SessionMessage.Message) { return `${String(DateTime.toEpochMillis(message.time.created)).padStart(16, "0")}:${message.id}` } diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx index c0aaa0ce5d6e..0f4819a48ab5 100644 --- a/packages/app/src/pages/session/timeline/message-timeline.tsx +++ b/packages/app/src/pages/session/timeline/message-timeline.tsx @@ -53,6 +53,8 @@ import type { UserMessage, } from "@opencode-ai/sdk/v2" import type { SessionMessageInfo } from "@opencode-ai/client/promise" +import type { Session } from "@opencode-ai/schema/session" +import type { SessionMessage } from "@opencode-ai/schema/session-message" import { showToast } from "@/utils/toast" import { downloadSessionExport, fetchSessionExport, sessionExportFilename } from "@/utils/session-export" import { getDirectory, getFilename } from "@opencode-ai/core/util/path" @@ -252,6 +254,8 @@ export function MessageTimeline(props: { centered: boolean messages?: Accessor sessionMessages?: Accessor + contextMessages: Accessor + contextSession: Accessor getMessageParts?: (messageID: string) => PartType[] setContentRef: (el: HTMLDivElement) => void userMessages: UserMessage[] @@ -1532,6 +1536,8 @@ export function MessageTimeline(props: { void setActiveMessage: (message: UserMessage | undefined) => void focusInput: () => void + getMessageParts?: (messageID: string) => Part[] review?: () => boolean fileBrowser?: () => boolean } @@ -342,7 +343,8 @@ export const useSessionCommands = (actions: SessionCommandContext) => { if (boundary < 0) return const message = messages[boundary - 1] if (!message) return - const parts = sync().data.part[message.id] + const projectedParts = actions.getMessageParts?.(message.id) + const parts = projectedParts?.length ? projectedParts : sync().data.part[message.id] if (sync().data.session_working(sessionID)) { await session.interrupt({ sessionID }).catch(() => {}) From de1304b4ba58183e53d1e00d5feeb12e39ef2bf0 Mon Sep 17 00:00:00 2001 From: henry701 Date: Mon, 24 Aug 2026 06:15:13 -0300 Subject: [PATCH 013/129] fix(tui): use reasoning provider metadata --- packages/tui/src/routes/session/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 433baa11a1c2..be9bca02f197 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -1670,7 +1670,7 @@ function ReasoningPart(props: { last: boolean; part: CurrentReasoning; message: // OpenRouter encrypts some reasoning blocks; drop the placeholder. return props.part.text.replace("[REDACTED]", "").trim() }) - const opaque = createMemo(() => !content() && Boolean(props.part.metadata)) + const opaque = createMemo(() => !content() && Boolean(props.part.providerMetadata)) // Reasoning is finalized when the server sets `time.end` (see processor.ts). // Flips independently of the parent message completing. const isDone = createMemo(() => props.part.time?.completed !== undefined) From 3999d0439ecaa0fa55e3c9c7884388e6893b49cc Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 3 Sep 2026 01:29:52 -0400 Subject: [PATCH 014/129] docs(go): recommend session header --- packages/web/src/content/docs/go.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index 58bb121b777e..6ec69aa04a35 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -100,7 +100,8 @@ Traffic is monitored for abusive traffic that degrades the experience for other To ensure your account does not get flagged, make sure the tool you're using 1\. does not generate abusive traffic -2\. properly identifies itself (no broad user agents) +2\. properly identifies itself (no broad user agents)
+3\. includes the `x-opencode-session` header so we can optimize prompt caching ## Usage limits From c94eb6133eca258cb06cc30d159aae7a76519d1b Mon Sep 17 00:00:00 2001 From: henry701 Date: Fri, 4 Sep 2026 21:43:46 -0300 Subject: [PATCH 015/129] fix(core): preserve catalog reasoning effort variants --- packages/core/src/plugin/models-dev.ts | 23 +++++- packages/core/test/plugin/models-dev.test.ts | 80 ++++++++++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/packages/core/src/plugin/models-dev.ts b/packages/core/src/plugin/models-dev.ts index 075a6ed093c6..9e565df78a1d 100644 --- a/packages/core/src/plugin/models-dev.ts +++ b/packages/core/src/plugin/models-dev.ts @@ -73,12 +73,28 @@ function modeName(model: ModelsDev.Model, mode: string) { return `${model.name} ${mode.charAt(0).toUpperCase()}${mode.slice(1)}` } +function reasoningVariants(model: ModelsDev.Model, provider: ModelsDev.Provider): ModelV2Info["variants"] { + const effort = model.reasoning_options?.find((option) => option.type === "effort") + if (!effort) return [] + const npm = model.provider?.npm ?? provider.npm + return effort.values.flatMap((value) => { + const id = value ?? "none" + // V2 variants are wire request bodies, not legacy AI SDK provider options. + const body = (() => { + if (npm === "@ai-sdk/openai") return { reasoning: { effort: id } } + if (npm === "@ai-sdk/openai-compatible") return { reasoning_effort: id } + })() + return body ? [{ id, headers: {}, body }] : [] + }) +} + function applyModel( draft: ModelV2Info, model: ModelsDev.Model, input: { readonly name?: string readonly cost?: ModelV2Info["cost"] + readonly variants?: ModelV2Info["variants"] readonly request?: NonNullable["modes"]>[string]["provider"] } = {}, ) { @@ -102,7 +118,7 @@ function applyModel( input: [...(model.modalities?.input ?? [])], output: [...(model.modalities?.output ?? [])], } - draft.variants = [] + draft.variants = input.variants ?? [] draft.time.released = released(model.release_date) draft.cost = input.cost ?? cost(model.cost) draft.status = model.status ?? "active" @@ -161,12 +177,15 @@ export const ModelsDevPlugin = define({ for (const model of Object.values(item.models)) { const baseCost = cost(model.cost) - catalog.model.update(providerID, model.id, (draft) => applyModel(draft, model, { cost: baseCost })) + catalog.model.update(providerID, model.id, (draft) => + applyModel(draft, model, { cost: baseCost, variants: reasoningVariants(model, item) }), + ) for (const [mode, options] of Object.entries(model.experimental?.modes ?? {})) { catalog.model.update(providerID, `${model.id}-${mode}`, (draft) => applyModel(draft, model, { name: modeName(model, mode), cost: mergeCost(baseCost, options.cost), + variants: reasoningVariants(model, item), request: options.provider, }), ) diff --git a/packages/core/test/plugin/models-dev.test.ts b/packages/core/test/plugin/models-dev.test.ts index 6c0f7e070296..7f475298beba 100644 --- a/packages/core/test/plugin/models-dev.test.ts +++ b/packages/core/test/plugin/models-dev.test.ts @@ -27,6 +27,86 @@ const layer = AppNodeBuilder.build(LayerNode.group([Catalog.node, Integration.no const it = testEffect(layer) describe("ModelsDevPlugin", () => { + for (const input of [ + { + name: "Muse Spark Responses", + npm: "@ai-sdk/openai", + values: ["minimal", "high", "xhigh"], + bodies: [ + { reasoning: { effort: "minimal" } }, + { reasoning: { effort: "high" } }, + { reasoning: { effort: "xhigh" } }, + ], + }, + { + name: "inherited Chat API", + npm: "@ai-sdk/openai-compatible", + values: [null, "high"], + bodies: [{ reasoning_effort: "none" }, { reasoning_effort: "high" }], + }, + { + name: "model transport override", + npm: "@ai-sdk/openai-compatible", + provider: { npm: "@ai-sdk/openai" }, + values: ["high"], + bodies: [{ reasoning: { effort: "high" } }], + }, + { name: "explicitly empty controls", npm: "@ai-sdk/openai", values: [], bodies: [] }, + { name: "unsupported transport", npm: "unknown-sdk", values: ["high"], bodies: [] }, + ]) { + it.effect("projects declared reasoning efforts for " + input.name, () => + Effect.gen(function* () { + const catalog = yield* Catalog.Service + const integrations = yield* Integration.Service + yield* ModelsDevPlugin.effect( + host({ catalog: catalogHost(catalog), integration: integrationHost(integrations) }), + ).pipe( + Effect.provideService( + ModelsDev.Service, + ModelsDev.Service.of({ + get: () => + Effect.succeed({ + acme: { + id: "acme", + name: "Acme", + env: [], + npm: input.npm, + api: "https://acme.test/v1", + models: { + muse: { + id: "muse", + name: "Muse Spark", + release_date: "2026-09-01", + attachment: false, + reasoning: true, + temperature: true, + tool_call: true, + provider: input.provider, + limit: { context: 100000, output: 4096 }, + reasoning_options: [{ type: "effort", values: input.values }], + experimental: { modes: { fast: { provider: { body: { service_tier: "priority" } } } } }, + }, + }, + }, + }), + refresh: () => Effect.void, + }), + ), + ) + for (const id of ["muse", "muse-fast"]) { + const model = yield* catalog.model.get(ProviderV2.ID.make("acme"), ModelV2.ID.make(id)) + expect(model?.variants).toEqual( + input.bodies.map((body, index) => ({ + id: ModelV2.VariantID.make(input.values[index] ?? "none"), + headers: {}, + body, + })), + ) + } + }), + ) + } + it.effect("projects models.dev modes as separate models instead of variants", () => Effect.gen(function* () { const integrations = yield* Integration.Service From 6264098d0645fab27391c0b6c06d751b88c05216 Mon Sep 17 00:00:00 2001 From: henry701 Date: Fri, 4 Sep 2026 23:51:51 -0300 Subject: [PATCH 016/129] fix(app): preserve admitted input and replacement session state --- .../e2e/performance/unit/mock-server.test.ts | 36 +++++ .../project-picker-recent-search.spec.ts | 4 +- .../review-state-persistence.spec.ts | 6 +- .../session-model-selection.spec.ts | 2 +- .../session-model-timeline-scroll.spec.ts | 24 ++-- .../regression/session-pending-reload.spec.ts | 48 +++++++ .../app/e2e/regression/session-rename.spec.ts | 4 +- .../regression/session-rollback-queue.spec.ts | 56 +++++++- ...sion-timeline-reasoning-projection.spec.ts | 6 +- .../app/e2e/smoke/session-timeline.fixture.ts | 18 ++- packages/app/e2e/tsconfig.json | 14 +- packages/app/e2e/utils/mock-server.ts | 108 +++++++++++---- .../src/context/global-sync/bootstrap.test.ts | 2 + .../app/src/context/global-sync/bootstrap.ts | 2 +- packages/app/src/context/models.tsx | 3 +- packages/app/src/pages/session.tsx | 19 ++- .../src/pages/session/current/model.test.ts | 60 ++++++++ .../app/src/pages/session/current/model.ts | 30 +++- .../src/pages/session/current/reducer.test.ts | 23 ++++ .../app/src/pages/session/current/reducer.ts | 12 +- .../pages/session/use-session-commands.tsx | 4 +- packages/app/src/utils/server-compat.test.ts | 14 ++ packages/app/src/utils/server-compat.ts | 5 +- packages/app/src/utils/server-revert.ts | 31 +++++ packages/app/src/utils/server.test.ts | 26 +++- packages/app/src/utils/server.ts | 37 ++--- .../client/src/generated-effect/client.ts | 3 +- packages/client/src/generated/client.ts | 2 +- packages/client/src/generated/types.ts | 130 +++++++++++++++++- packages/core/src/session.ts | 8 +- packages/core/src/session/input.ts | 21 +++ packages/core/src/session/projector.ts | 17 +++ .../core/src/session/revert-replacement.ts | 52 +++++++ packages/core/src/session/revert.ts | 21 ++- packages/core/test/session-prompt.test.ts | 37 +++++ .../core/test/session-replacement.test.ts | 110 +++++++++++++++ packages/core/test/session-runner.test.ts | 48 +++++++ .../test/server/httpapi-exercise/index.ts | 9 +- packages/protocol/src/groups/message.ts | 1 + packages/protocol/src/groups/session.ts | 6 +- packages/schema/src/revert.ts | 2 + packages/sdk/js/src/v2/gen/sdk.gen.ts | 2 + packages/sdk/js/src/v2/gen/types.gen.ts | 4 + packages/server/src/handlers/message.ts | 6 + .../server/src/handlers/pending-inputs.ts | 17 +++ packages/server/test/pending-inputs.test.ts | 19 +++ 46 files changed, 1007 insertions(+), 102 deletions(-) create mode 100644 packages/app/e2e/regression/session-pending-reload.spec.ts create mode 100644 packages/app/src/utils/server-revert.ts create mode 100644 packages/core/src/session/revert-replacement.ts create mode 100644 packages/core/test/session-replacement.test.ts create mode 100644 packages/server/src/handlers/pending-inputs.ts create mode 100644 packages/server/test/pending-inputs.test.ts diff --git a/packages/app/e2e/performance/unit/mock-server.test.ts b/packages/app/e2e/performance/unit/mock-server.test.ts index 8f57fef21c5d..5567ad5700f6 100644 --- a/packages/app/e2e/performance/unit/mock-server.test.ts +++ b/packages/app/e2e/performance/unit/mock-server.test.ts @@ -44,3 +44,39 @@ test("applies message latency after a list response gate is released", async () expect(performance.now() - released).toBeGreaterThanOrEqual(20) expect(events).toEqual(["start", "before", "page", "end", "fulfill"]) }) + +test("V2 fixtures preserve configured reasoning variants and custom agents", async () => { + let handler: ((route: Route) => Promise) | undefined + const page = { + route: (_url: string, callback: typeof handler) => { + handler = callback + return Promise.resolve() + }, + } as unknown as Page + await mockOpenCodeServer(page, { + directory: "/fixture", + project: {}, + sessions: [], + provider: { + all: [{ id: "test", models: { muse: { id: "muse", variants: { high: { reasoningEffort: "high" } } } } }], + connected: ["test"], + default: { providerID: "test", modelID: "muse" }, + }, + agents: [{ name: "reviewer", mode: "primary" }], + }) + const request = async (path: string) => { + let body = "" + await handler!({ + request: () => ({ url: () => `http://127.0.0.1:4096${path}`, method: () => "GET" }), + fulfill: (response: { body: string }) => { + body = response.body + return Promise.resolve() + }, + } as unknown as Route) + return JSON.parse(body) + } + expect((await request("/api/model")).data[0].variants).toEqual([ + { id: "high", settings: { reasoningEffort: "high" }, headers: {}, body: {} }, + ]) + expect((await request("/api/agent")).data[0].id).toBe("reviewer") +}) diff --git a/packages/app/e2e/regression/project-picker-recent-search.spec.ts b/packages/app/e2e/regression/project-picker-recent-search.spec.ts index 2cdb0b4a03b4..435d12fb66c1 100644 --- a/packages/app/e2e/regression/project-picker-recent-search.spec.ts +++ b/packages/app/e2e/regression/project-picker-recent-search.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test" import type { Page } from "@playwright/test" -import { fixture, pageMessages } from "../smoke/session-timeline.fixture" +import { fixture, currentPageMessages } from "../smoke/session-timeline.fixture" import { mockOpenCodeServer } from "../utils/mock-server" import { expectAppVisible } from "../utils/waits" @@ -22,7 +22,7 @@ async function openProjectDialog(page: Page) { provider: fixture.provider, directory: fixture.directory, project: fixture.project, - pageMessages, + currentPageMessages, fileList: () => [], findFiles: () => [], }) diff --git a/packages/app/e2e/regression/review-state-persistence.spec.ts b/packages/app/e2e/regression/review-state-persistence.spec.ts index 57abee5b678a..ce944a892406 100644 --- a/packages/app/e2e/regression/review-state-persistence.spec.ts +++ b/packages/app/e2e/regression/review-state-persistence.spec.ts @@ -93,7 +93,11 @@ async function setup(page: Page) { route.fulfill({ status: 200, contentType: "application/json", - body: JSON.stringify({ branch: "feature", default_branch: "dev" }), + body: JSON.stringify( + new URL(route.request().url()).pathname.startsWith("/api/") + ? { location: { directory }, data: { branch: "feature", defaultBranch: "dev" } } + : { branch: "feature", default_branch: "dev" }, + ), }), ) await page.route("**/vcs/diff**", (route) => diff --git a/packages/app/e2e/regression/session-model-selection.spec.ts b/packages/app/e2e/regression/session-model-selection.spec.ts index 34fe9e5e7e18..291760ceae9a 100644 --- a/packages/app/e2e/regression/session-model-selection.spec.ts +++ b/packages/app/e2e/regression/session-model-selection.spec.ts @@ -183,12 +183,12 @@ test("keeps a manually selected model when submitting a brand-new web session", default: { opencode: "big-pickle" }, }), agents: [ + { name: "build", mode: "primary" }, { name: "Sisyphus - ultraworker", mode: "primary", model: { providerID: "opencode", modelID: "big-pickle" }, }, - { name: "build", mode: "primary" }, ], sessions, createSession: () => { diff --git a/packages/app/e2e/regression/session-model-timeline-scroll.spec.ts b/packages/app/e2e/regression/session-model-timeline-scroll.spec.ts index 21a4af1dc395..f2da546ef72b 100644 --- a/packages/app/e2e/regression/session-model-timeline-scroll.spec.ts +++ b/packages/app/e2e/regression/session-model-timeline-scroll.spec.ts @@ -21,11 +21,13 @@ const messages = Array.from({ length: 80 }, (_, index) => { id: userID, created: 1700000000000 + index * 10_000, }), - assistantMessage([textPart(`prt_history_${index}_assistant`, `Assistant reply ${index}. ${"response ".repeat(40)}`)], { - id: `msg_history_${index}_assistant`, - parentID: userID, - created: 1700000001000 + index * 10_000, - }), + assistantMessage( + [textPart(`prt_history_${index}_assistant`, `Assistant reply ${index}. ${"response ".repeat(40)}`)], + { + id: `msg_history_${index}_assistant`, + created: 1700000001000 + index * 10_000, + }, + ), ] }).flat() @@ -211,12 +213,16 @@ test("keeps scrolled timeline when switching between variant and non-variant mod const modelDialog = () => page.getByRole("dialog", { name: /Select model/i }) await composer.locator('[data-action="prompt-model"]').click() - await modelDialog().getByRole("button", { name: /Big Pickle/ }).click() + await modelDialog() + .getByRole("button", { name: /Big Pickle/ }) + .click() await expect(composer.locator('[data-action="prompt-model"]')).toContainText("Big Pickle") await page.waitForTimeout(200) await composer.locator('[data-action="prompt-model"]').click() - await modelDialog().getByRole("button", { name: /Claude Opus 4.6/ }).click() + await modelDialog() + .getByRole("button", { name: /Claude Opus 4.6/ }) + .click() await expect(composer.locator('[data-action="prompt-model"]')).toContainText("Claude Opus 4.6") await page.waitForTimeout(200) @@ -226,7 +232,9 @@ test("keeps scrolled timeline when switching between variant and non-variant mod expect(withVariant.visible.length).toBeGreaterThan(2) await composer.locator('[data-action="prompt-model"]').click() - await modelDialog().getByRole("button", { name: /DeepSeek V4 Flash Free/ }).click() + await modelDialog() + .getByRole("button", { name: /DeepSeek V4 Flash Free/ }) + .click() await expect(composer.locator('[data-action="prompt-model"]')).toContainText("DeepSeek V4 Flash Free") await page.waitForTimeout(200) diff --git a/packages/app/e2e/regression/session-pending-reload.spec.ts b/packages/app/e2e/regression/session-pending-reload.spec.ts new file mode 100644 index 000000000000..112a65fcdd36 --- /dev/null +++ b/packages/app/e2e/regression/session-pending-reload.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from "@playwright/test" +import { base64Encode } from "@opencode-ai/core/util/encode" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/PendingReload" +const sessionID = "ses_pending_reload" + +test("keeps stopped steering visible across page reload without resuming inference", async ({ page }) => { + await mockOpenCodeServer(page, { + directory, + project: { + id: "proj_pending", + worktree: directory, + name: "Pending reload", + time: { created: 1, updated: 1 }, + sandboxes: [], + }, + provider: { + all: [ + { id: "opencode", name: "OpenCode", models: { test: { id: "test", name: "Test", variants: { high: {} } } } }, + ], + connected: ["opencode"], + default: { providerID: "opencode", modelID: "test" }, + }, + sessions: [ + { + id: sessionID, + projectID: "proj_pending", + directory, + title: "Stopped steering", + time: { created: 1, updated: 1 }, + }, + ], + currentPageMessages: () => ({ + items: [], + throughSeq: 2, + pending: [{ id: "msg_pending", type: "user", text: "Keep this admitted steering input", time: { created: 1 } }], + }), + }) + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await expectSessionTitle(page, "Stopped steering") + await expect(page.getByText("Keep this admitted steering input", { exact: true })).toHaveCount(1) + await expect(page.getByRole("button", { name: "Stop", exact: true })).toHaveCount(0) + await page.reload() + await expect(page.getByText("Keep this admitted steering input", { exact: true })).toHaveCount(1) + await expect(page.getByRole("button", { name: "Stop", exact: true })).toHaveCount(0) +}) diff --git a/packages/app/e2e/regression/session-rename.spec.ts b/packages/app/e2e/regression/session-rename.spec.ts index 2cd97c24c1c0..2fd366f6be11 100644 --- a/packages/app/e2e/regression/session-rename.spec.ts +++ b/packages/app/e2e/regression/session-rename.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test" -import { fixture, pageMessages } from "../smoke/session-timeline.fixture" +import { fixture, currentPageMessages } from "../smoke/session-timeline.fixture" import { mockOpenCodeServer } from "../utils/mock-server" test.beforeEach(async ({ page }) => { @@ -10,7 +10,7 @@ test.beforeEach(async ({ page }) => { provider: fixture.provider, directory: fixture.directory, project: fixture.project, - pageMessages, + currentPageMessages, }) await page.route(/\/session\/[^/]+(?:\?.*)?$/, async (route) => { if (route.request().method() !== "PATCH") return route.fallback() diff --git a/packages/app/e2e/regression/session-rollback-queue.spec.ts b/packages/app/e2e/regression/session-rollback-queue.spec.ts index bda850ddb647..c3256bfbd580 100644 --- a/packages/app/e2e/regression/session-rollback-queue.spec.ts +++ b/packages/app/e2e/regression/session-rollback-queue.spec.ts @@ -62,7 +62,13 @@ const messages = [ ] test("preserves visible queued follow-ups when rolling back a message", async ({ page }) => { + const prompts: unknown[] = [] + const stages: unknown[] = [] + page.on("request", (request) => { + if (request.url().endsWith("/revert/stage")) stages.push(request.postDataJSON()) + }) await mockOpenCodeServer(page, { + onPrompt: ({ body }) => prompts.push(body), directory, project: { id: projectID, @@ -83,7 +89,7 @@ test("preserves visible queued follow-ups when rolling back a message", async ({ connected: ["opencode"], default: model, }, - sessions: [session], + sessions: [{ ...session }], status: { [sessionID]: { type: "busy" } }, queue: { [sessionID]: [{ id: "msg_queue_web_rollback", text: queuedText }] }, currentPageMessages: () => ({ items: messages.toReversed(), throughSeq: 0 }), @@ -97,5 +103,53 @@ test("preserves visible queued follow-ups when rolling back a message", async ({ await page.getByRole("button", { name: "Revert message", exact: true }).first().click({ force: true }) await expectAppVisible(page.locator('[data-component="session-revert-dock"]')) + await expect(page.locator('[data-component="session-revert-dock"]')).toContainText("first user prompt") + const input = page.locator('[data-component="prompt-input"]') + await expect(input).toContainText("first user prompt") + await input.fill("edited first user prompt") + await input.press("Enter") + await expect.poll(() => prompts.length).toBe(1) + expect(stages).toEqual([ + { messageID: "msg_user_0001", inclusive: true }, + { messageID: "msg_user_0001", inclusive: true }, + ]) + expect(prompts[0]).toMatchObject({ + payload: { parts: [expect.objectContaining({ type: "text", text: "edited first user prompt" })] }, + }) await expect(page.locator('[data-component="session-followup-dock"]')).toContainText(queuedText) }) + +test("does not stage rollback when inference interruption fails", async ({ page }) => { + let staged = 0 + await mockOpenCodeServer(page, { + directory, + project: { id: projectID, worktree: directory, time: { created: 1, updated: 1 }, sandboxes: [] }, + provider: { + all: [{ id: "opencode", name: "OpenCode", models: { "test-model": { id: "test-model", name: "Test Model" } } }], + connected: ["opencode"], + default: model, + }, + sessions: [{ ...session }], + status: { [sessionID]: { type: "busy" } }, + currentPageMessages: () => ({ items: messages.toReversed(), throughSeq: 0 }), + }) + page.on("request", (request) => { + if (request.url().endsWith("/revert/stage")) staged++ + }) + await page.route(/\/interrupt(?:\?.*)?$/, (route) => + route.fulfill({ + status: 500, + contentType: "application/json", + body: JSON.stringify({ _tag: "UnknownError", message: "Cannot interrupt", ref: "test" }), + }), + ) + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await expectSessionTitle(page, session.title) + const input = page.locator('[data-component="prompt-input"]') + await input.fill("Keep my existing draft") + await page.getByRole("button", { name: "Revert message", exact: true }).first().click({ force: true }) + await expect(page.getByText("Request failed", { exact: true })).toBeVisible() + expect(staged).toBe(0) + await expect(input).toContainText("Keep my existing draft") + await expect(page.locator('[data-component="session-revert-dock"]')).toHaveCount(0) +}) diff --git a/packages/app/e2e/regression/session-timeline-reasoning-projection.spec.ts b/packages/app/e2e/regression/session-timeline-reasoning-projection.spec.ts index b1d767456c2c..f43ea22390fa 100644 --- a/packages/app/e2e/regression/session-timeline-reasoning-projection.spec.ts +++ b/packages/app/e2e/regression/session-timeline-reasoning-projection.spec.ts @@ -69,7 +69,7 @@ for (const profile of profiles) { await timeline.sendStatus("busy", 150) await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(profile.thinking ? 1 : 0) - await expect(page.locator(`[data-timeline-part-id="${reasoningID}"]`)).toHaveCount(profile.body ? 1 : 0) + await expect(page.locator('[data-component="reasoning-part"]')).toHaveCount(profile.body ? 1 : 0) if (!profile.summaries && profile.reasoning.trim()) { await expect(page.getByText("Inspecting stability", { exact: true })).toBeVisible() } @@ -87,6 +87,6 @@ test("does not infer reasoning visibility from provider identity", async ({ page await timeline.sendStatus("busy", 150) await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) - await expect(page.locator('[data-timeline-part-id*="reasoning"]')).toHaveCount(0) - await expect(page.locator('[data-timeline-part-id="prt_provider_text"]')).toBeVisible() + await expect(page.locator('[data-component="reasoning-part"]')).toHaveCount(0) + await expect(page.getByText("No reasoning payload", { exact: true })).toBeVisible() }) diff --git a/packages/app/e2e/smoke/session-timeline.fixture.ts b/packages/app/e2e/smoke/session-timeline.fixture.ts index 36569c578e05..0867f6df28e4 100644 --- a/packages/app/e2e/smoke/session-timeline.fixture.ts +++ b/packages/app/e2e/smoke/session-timeline.fixture.ts @@ -30,7 +30,15 @@ const model = { providerID: "opencode", modelID: "claude-opus-4-6", variant: "ma type MessagePart = Record & { id: string; type: string; text?: string; name?: string } type Message = - | { id: string; type: "user"; text: string; payload: Record; time: { created: number } } + | { + id: string + type: "user" + text: string + files: unknown[] + agents: unknown[] + payload: Record + time: { created: number } + } | { id: string type: "assistant" @@ -225,8 +233,8 @@ const sourceMessages = Array.from({ length: 12 }, (_, index) => [ function renderable(part: MessagePart) { if (part.type === "tool" && part.name === "todowrite") return false - if (part.type === "text") return !!part.text.trim() - if (part.type === "reasoning") return !!part.text.trim() + if (part.type === "text") return !!part.text?.trim() + if (part.type === "reasoning") return !!part.text?.trim() return part.type !== "step-start" && part.type !== "step-finish" && part.type !== "patch" } @@ -284,9 +292,7 @@ export const fixture = { expected: { sourceTitle: "Uncommitted changes inquiry", targetTitle: "Example Game: sample jump movement & sample physics analysis", - targetMessageIDs: targetMessages - .filter((message) => message.type === "user") - .map((message) => message.id), + targetMessageIDs: targetMessages.filter((message) => message.type === "user").map((message) => message.id), targetPartIDs: targetMessages.flatMap((message) => orderedParts(message) .filter(renderable) diff --git a/packages/app/e2e/tsconfig.json b/packages/app/e2e/tsconfig.json index 4a6046e4faaf..08cdf76b7f26 100644 --- a/packages/app/e2e/tsconfig.json +++ b/packages/app/e2e/tsconfig.json @@ -3,7 +3,9 @@ "compilerOptions": { "noEmit": true, "rootDir": "..", - "types": ["node", "bun"] + "types": ["node", "bun"], + "composite": false, + "emitDeclarationOnly": false }, "include": [ "./performance/timeline-stability/**/*.spec.ts", @@ -15,6 +17,14 @@ "../src/pages/session/timeline/observe-element-offset.ts", "./regression/new-session-panel-corner.spec.ts", "./regression/session-timeline-context-resize.spec.ts", - "./utils/**/*.ts" + "./utils/**/*.ts", + "./regression/session-pending-reload.spec.ts", + "./regression/session-rollback-queue.spec.ts", + "./regression/session-model-selection.spec.ts", + "./regression/session-timeline-reasoning-projection.spec.ts", + "./regression/review-state-persistence.spec.ts", + "./regression/session-rename.spec.ts", + "./regression/project-picker-recent-search.spec.ts", + "./regression/session-model-timeline-scroll.spec.ts" ] } diff --git a/packages/app/e2e/utils/mock-server.ts b/packages/app/e2e/utils/mock-server.ts index d4f47f172a12..9d06d8e8dcb3 100644 --- a/packages/app/e2e/utils/mock-server.ts +++ b/packages/app/e2e/utils/mock-server.ts @@ -25,6 +25,7 @@ export interface MockServerConfig { items: unknown[] cursor?: { previous?: string; next?: string } throughSeq: number + pending?: unknown[] } vcsDiff?: unknown[] status?: Record @@ -143,19 +144,22 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { }, data: [], }) - if (path === "/api/provider") return json(route, { location: location(config), data: currentCatalog(config).providers }) + if (path === "/api/provider") + return json(route, { location: location(config), data: currentCatalog(config).providers }) if (path === "/api/model") return json(route, { location: location(config), data: currentCatalog(config).models }) - if (path === "/api/model/default") return json(route, { location: location(config), data: currentCatalog(config).default }) + if (path === "/api/model/default") + return json(route, { location: location(config), data: currentCatalog(config).default }) if (path === "/api/fs/list" && config.fileList) { const files = await config.fileList(url.searchParams.get("path") ?? "") return json(route, { location: location(config), - data: files instanceof Array - ? files.map((entry) => { - const item = entry as { path: string; type: "file" | "directory" } - return { path: item.path, type: item.type } - }) - : [], + data: + files instanceof Array + ? files.map((entry) => { + const item = entry as { path: string; type: "file" | "directory" } + return { path: item.path, type: item.type } + }) + : [], }) } if (path === "/api/fs/find" && config.findFiles) { @@ -172,16 +176,24 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { if (path === "/api/agent") return json(route, { location: location(config), - data: [ - { - id: "build", - name: "Build", - mode: "primary", - hidden: false, + data: (config.agents ?? [{ name: "build", mode: "primary", hidden: false }]).map((value) => { + const agent = value as { + id?: string + name: string + mode?: string + hidden?: boolean + model?: { providerID: string; modelID: string } + variant?: string + } + return { + ...agent, + id: agent.id ?? agent.name, request: { settings: {}, headers: {}, body: {} }, permissions: [], - }, - ], + model: agent.model && { providerID: agent.model.providerID, id: agent.model.modelID }, + variant: agent.variant, + } + }), }) if (path === "/api/command") return json(route, { location: location(config), data: [] }) if (path === "/api/mcp") return json(route, { location: location(config), data: [] }) @@ -257,7 +269,11 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { }) } if (path === "/api/session/active") { - const statuses = (config.sessionStatus ?? config.status ?? {}) as Record + const statuses = ( + typeof config.sessionStatus === "function" + ? config.sessionStatus() + : (config.sessionStatus ?? config.status ?? {}) + ) as Record return json(route, { data: Object.fromEntries( Object.entries(statuses).flatMap(([id, status]) => @@ -269,7 +285,9 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { const queueMatch = path.match(/^\/api\/session\/([^/]+)\/queue$/) if (queueMatch && route.request().method() === "GET") return json(route, { - data: (config.queue?.[queueMatch[1]] ?? []).map((item, position) => queuedInput(queueMatch[1]!, item, position)), + data: (config.queue?.[queueMatch[1]] ?? []).map((item, position) => + queuedInput(queueMatch[1]!, item, position), + ), }) if (queueMatch && route.request().method() === "POST") return json(route, { data: { id: "msg_queue_mock" } }) const queueSendMatch = path.match(/^\/api\/session\/([^/]+)\/queue\/([^/]+)\/send$/) @@ -328,6 +346,18 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { } if (path in staticRoutes) return json(route, staticRoutes[path]) + const revertMatch = path.match(/^\/api\/session\/([^/]+)\/revert\/stage$/) + if (revertMatch && route.request().method() === "POST") { + const session = config.sessions.find((item) => item.id === revertMatch[1]) + if (!session) return json(route, { error: "Session not found" }, undefined, 404) + session.revert = { messageID: route.request().postDataJSON().messageID } + return json(route, { data: session.revert }) + } + const contextMatch = path.match(/^\/api\/session\/([^/]+)\/context$/) + if (contextMatch) { + const messages = config.currentPageMessages?.(contextMatch[1]!, Number.MAX_SAFE_INTEGER) + return json(route, { data: messages?.items.toReversed() ?? [] }) + } const currentSessionMatch = path.match(/^\/api\/session\/([^/]+)$/) if (currentSessionMatch) { const session = config.sessions.find((item) => item.id === currentSessionMatch[1]) @@ -379,7 +409,12 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { token, ) ?? { items: [], throughSeq: 0 } config.onMessages?.({ sessionID: currentMessagesMatch[1]!, before: token, phase: "end" }) - return json(route, { data: pageData.items, throughSeq: pageData.throughSeq, cursor: pageData.cursor ?? {} }) + return json(route, { + data: pageData.items, + pending: pageData.pending, + throughSeq: pageData.throughSeq, + cursor: pageData.cursor ?? {}, + }) } const before = token ? cursors.get(token) : undefined if (token && !before) return json(route, { error: "Invalid cursor" }, undefined, 400) @@ -387,7 +422,11 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { await config.beforeMessagesResponse?.({ sessionID: currentMessagesMatch[1]!, before }) if (config.messageDelay !== undefined) await new Promise((resolve) => setTimeout(resolve, config.messageDelay)) config.onMessages?.({ sessionID: currentMessagesMatch[1], before, phase: "end" }) - const pageData = config.pageMessages?.(currentMessagesMatch[1], Number(url.searchParams.get("limit") ?? 50), before) ?? { + const pageData = config.pageMessages?.( + currentMessagesMatch[1], + Number(url.searchParams.get("limit") ?? 50), + before, + ) ?? { items: [], } const cursor = pageData.cursor ? `cursor_${++nextCursor}` : undefined @@ -449,7 +488,22 @@ function currentCatalog(config: MockServerConfig) { const value = typeof config.provider === "function" ? config.provider() : config.provider if (!value || typeof value !== "object") return { providers: [], models: [], default: null } const catalog = value as { - all?: { id?: string; name?: string; package?: string; models?: Record }[] + all?: { + id?: string + name?: string + package?: string + models?: Record< + string, + { + id?: string + name?: string + family?: string + release_date?: string + variants?: Record> + limit?: unknown + } + > + }[] default?: { providerID?: string; modelID?: string } } const providers = catalog.all ?? [] @@ -461,8 +515,9 @@ function currentCatalog(config: MockServerConfig) { name: model.name ?? model.id ?? id, package: provider.package ?? "", capabilities: { tools: true, input: ["text"], output: ["text"] }, - variants: [], - time: { released: 0 }, + variants: Object.entries(model.variants ?? {}).map(([id, settings]) => ({ id, settings, headers: {}, body: {} })), + family: model.family ?? model.id ?? id, + time: { released: model.release_date ? Date.parse(model.release_date) : Date.now() }, cost: [], status: "active" as const, enabled: true, @@ -476,9 +531,10 @@ function currentCatalog(config: MockServerConfig) { package: provider.package ?? "", })), models, - default: models.find( - (model) => model.providerID === catalog.default?.providerID && model.id === catalog.default.modelID, - ) ?? null, + default: + models.find( + (model) => model.providerID === catalog.default?.providerID && model.id === catalog.default.modelID, + ) ?? null, } } diff --git a/packages/app/src/context/global-sync/bootstrap.test.ts b/packages/app/src/context/global-sync/bootstrap.test.ts index 3f39e50fb6de..7bcbaf6de3b6 100644 --- a/packages/app/src/context/global-sync/bootstrap.test.ts +++ b/packages/app/src/context/global-sync/bootstrap.test.ts @@ -168,6 +168,7 @@ describe("bootstrapDirectory", () => { provider, }, sdk: { + vcs: { get: async () => ({ data: { branch: "feature", default_branch: "dev" } }) }, config: { get: async () => { throw new Error("legacy directory config should not be called") @@ -189,6 +190,7 @@ describe("bootstrapDirectory", () => { await new Promise((resolve) => setTimeout(resolve, 80)) expect(store.status).toBe("complete") + expect(store.vcs).toEqual({ branch: "feature", default_branch: "dev" }) }) }) diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index 0f3e47381649..962c84db6e09 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -425,7 +425,7 @@ export async function bootstrapDirectory(input: { })), () => retry(async () => { - if ((await input.protocol) !== "v1") return + // Branch metadata still comes from the compatibility endpoint on V2 servers. return input.sdk.vcs.get().then((result) => { const next = { branch: result.data?.branch, default_branch: result.data?.default_branch } input.setStore("vcs", next) diff --git a/packages/app/src/context/models.tsx b/packages/app/src/context/models.tsx index a80cf2e5804d..b8e80d463edb 100644 --- a/packages/app/src/context/models.tsx +++ b/packages/app/src/context/models.tsx @@ -161,7 +161,8 @@ export const { use: useModels, provider: ModelsProvider } = createSimpleContext( visible, setVisibility, recent: { - list: () => recentModels()!, + // A recent-model update must not suspend and detach the session timeline. + list: () => recentModels.latest, push, }, variant: { diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 3ea95a64ab60..383a3bb30bf0 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -64,6 +64,7 @@ import { useSettingsCommand } from "@/components/settings-dialog" import { setCursorPosition } from "@/components/prompt-input/editor-dom" import { promptLength } from "@/components/prompt-input/history" import { type FollowupDraft } from "@/components/prompt-input/submit" +import { SessionMessage } from "@opencode-ai/schema/session-message" import { promptFromSessionMessage } from "@/components/prompt-input/prompt-from-session-payload" import { createPromptInputController, @@ -1729,6 +1730,7 @@ export default function Page() { const merge = (next: Session, target = sync()) => target.session.remember(next) const roll = (sessionID: string, next: NonNullable>["revert"], target = sync()) => { + current.setRevert(sessionID, next && { ...next, messageID: SessionMessage.ID.make(next.messageID) }) const session = target.session.get(sessionID) if (!session) return target.session.remember({ ...session, revert: next }) @@ -1859,12 +1861,7 @@ export default function Page() { setEditingFollowup(undefined) } - const halt = (sessionID: string) => - busy(sessionID) - ? sdk() - .api.session.interrupt({ sessionID }) - .catch(() => {}) - : Promise.resolve() + const halt = (sessionID: string) => (busy(sessionID) ? sdk().api.session.interrupt({ sessionID }) : Promise.resolve()) const revertMutation = useMutation(() => ({ mutationFn: async (input: { sessionID: string; messageID: string }) => { @@ -1878,7 +1875,7 @@ export default function Page() { roll(input.sessionID, { messageID: input.messageID }, target) prompt.set(value) }, - request: () => halt(input.sessionID).then(() => session.revert.stage(input)), + request: () => halt(input.sessionID).then(() => session.revert.stage({ ...input, inclusive: true })), complete: () => setEditingRevert(input.messageID), rollback: () => roll(input.sessionID, last, target), fail, @@ -1911,7 +1908,9 @@ export default function Page() { request: () => !next ? halt(sessionID).then(() => session.revert.clear({ sessionID })) - : halt(sessionID).then(() => session.revert.stage({ sessionID, messageID: next.id }).then(() => undefined)), + : halt(sessionID).then(() => + session.revert.stage({ sessionID, messageID: next.id, inclusive: true }).then(() => undefined), + ), complete: () => setEditingRevert(next?.id), rollback: () => roll(sessionID, last, target), fail, @@ -2244,7 +2243,7 @@ export default function Page() { const id = params.id if (!id) return Promise.resolve() return sdk() - .api.session.revert.stage({ sessionID: id, messageID }) + .api.session.revert.stage({ sessionID: id, messageID, inclusive: true }) .then(() => undefined) }} onRevertSubmitComplete={() => setEditingRevert(undefined)} @@ -2287,7 +2286,7 @@ export default function Page() { const id = params.id if (!id) return Promise.resolve() return sdk() - .api.session.revert.stage({ sessionID: id, messageID }) + .api.session.revert.stage({ sessionID: id, messageID, inclusive: true }) .then(() => undefined) }, onRevertSubmitComplete: () => setEditingRevert(undefined), diff --git a/packages/app/src/pages/session/current/model.test.ts b/packages/app/src/pages/session/current/model.test.ts index 4844eaf73c7d..9cafe812aa2f 100644 --- a/packages/app/src/pages/session/current/model.test.ts +++ b/packages/app/src/pages/session/current/model.test.ts @@ -83,6 +83,43 @@ function makePort(input?: { } describe("current session model", () => { + test("reloads authoritative messages and pending inputs after a committed replacement", async () => { + const commit = Promise.withResolvers() + let committed = false + const port = makePort() + port.sessions.events = async function* (_input, options) { + await commit.promise + yield { + id: "evt_commit", + type: "session.next.revert.committed", + durable: { aggregateID: "ses_test", seq: 3, version: 1 }, + data: { sessionID: "ses_test", messageID: "msg_old", timestamp: 3 }, + } + await new Promise((resolve) => options?.signal?.addEventListener("abort", () => resolve(), { once: true })) + } + port.messages.list = async () => ({ + ...messages(committed ? [] : [{ id: "msg_old", text: "old", created: 1 }], undefined, committed ? 3 : 1), + pending: committed ? [] : [{ id: "msg_pending", type: "user", text: "pending", time: { created: 2 } }], + }) + await new Promise((resolve, reject) => + createRoot((dispose) => { + const model = createCurrentSessionModel({ sessionID: () => "ses_test", client: () => port, autoStart: false }) + model + .start() + .then(async () => { + expect(model.messages()).toHaveLength(2) + committed = true + commit.resolve() + await until(() => model.messages().length === 0) + model.dispose() + dispose() + resolve() + }) + .catch(reject) + }), + ) + }) + test("buffers SSE during hydration and refreshes server-authoritative queue state", async () => { let queueReads = 0 const event = prompted(4) @@ -144,6 +181,29 @@ describe("current session model", () => { }) }) + test("rehydrates stopped steering from the snapshot without marking inference busy", async () => { + const page = { + ...messages([], undefined, 5), + pending: messages([{ id: "msg_steer", text: "admitted before reload", created: 2 }]).data, + } + const port = makePort({ pages: [page] }) + await new Promise((resolve, reject) => { + createRoot((dispose) => { + const model = createCurrentSessionModel({ sessionID: () => "ses_test", client: () => port, autoStart: false }) + model + .start() + .then(() => { + expect(model.messages()).toMatchObject([{ id: "msg_steer", text: "admitted before reload" }]) + expect(model.busy()).toBe(false) + model.dispose() + dispose() + resolve() + }) + .catch(reject) + }) + }) + }) + test("loads older pages in chronological order", async () => { const port = makePort({ pages: [ diff --git a/packages/app/src/pages/session/current/model.ts b/packages/app/src/pages/session/current/model.ts index b6c0a5d7ee24..09d94fdab3e5 100644 --- a/packages/app/src/pages/session/current/model.ts +++ b/packages/app/src/pages/session/current/model.ts @@ -17,6 +17,7 @@ import { const pageSize = 100 const decodeSession = Schema.decodeUnknownSync(Session.Info) const decodeMessages = Schema.decodeUnknownSync(Schema.Array(SessionMessage.Message)) +const decodePending = Schema.decodeUnknownSync(Schema.Array(SessionMessage.User)) const decodeQueue = Schema.decodeUnknownSync(Schema.Array(SessionInput.Queued)) const decodeEvent = Schema.decodeUnknownSync(SessionEvent.All) @@ -108,6 +109,7 @@ export function createCurrentSessionModel(input: { const page = await client.messages.list({ sessionID, order: "desc", limit: pageSize }, { signal }) return { messages: decodeMessages(page.data).toReversed(), + pending: "pending" in page ? decodePending(page.pending) : undefined, cursor: page.cursor.next ?? undefined, throughSeq: page.throughSeq, } @@ -123,6 +125,17 @@ export function createCurrentSessionModel(input: { const session = events.some(eventRefreshesSession) const active = events.some(eventRefreshesActive) const context = events.some(eventRefreshesContext) + if (events.some((event) => event.type === "session.next.revert.committed")) { + const page = await newest(client, sessionID, signal) + if (signal?.aborted || input.sessionID() !== sessionID) return + dispatch({ + type: "newest-merged", + messages: page.messages, + pending: page.pending, + sequence: page.throughSeq, + hasOlder: page.cursor !== undefined, + }) + } await Promise.all([ queue ? refreshQueue(client, sessionID, signal) : undefined, session ? refreshSession(client, sessionID, signal) : undefined, @@ -227,6 +240,7 @@ export function createCurrentSessionModel(input: { dispatch({ type: "hydrated", messages: page.messages, + pending: page.pending, cursor: page.cursor ?? undefined, sequence: page.throughSeq, }) @@ -236,7 +250,13 @@ export function createCurrentSessionModel(input: { const events = buffered.splice(0) const page = await newest(client, sessionID, controller.signal) if (controller.signal.aborted || generation !== activeGeneration) return - dispatch({ type: "newest-merged", messages: page.messages, hasOlder: page.cursor !== undefined }) + dispatch({ + type: "newest-merged", + messages: page.messages, + pending: page.pending, + sequence: page.throughSeq, + hasOlder: page.cursor !== undefined, + }) events.forEach((event) => dispatch({ type: "event", event })) if (events.some((event) => event.type === "session.next.step.started")) dispatch({ type: "active-updated", active: true }) @@ -290,7 +310,13 @@ export function createCurrentSessionModel(input: { refreshActive(client, sessionID, controller.signal), refreshContext(client, sessionID, controller.signal), ]) - dispatch({ type: "newest-merged", messages: page.messages, hasOlder: page.cursor !== undefined }) + dispatch({ + type: "newest-merged", + messages: page.messages, + pending: page.pending, + sequence: page.throughSeq, + hasOlder: page.cursor !== undefined, + }) } const dispose = () => { diff --git a/packages/app/src/pages/session/current/reducer.test.ts b/packages/app/src/pages/session/current/reducer.test.ts index 99841a4df1e7..9660f0cbff43 100644 --- a/packages/app/src/pages/session/current/reducer.test.ts +++ b/packages/app/src/pages/session/current/reducer.test.ts @@ -22,6 +22,29 @@ function dispatch(actions: CurrentSessionAction[]) { } describe("current session reducer", () => { + test("does not restore discarded pending input from a stale refresh", () => { + const pending = Schema.decodeUnknownSync(SessionMessage.User)({ + id: "msg_steer", + type: "user", + text: "pending", + time: { created: 1 }, + }) + const state = dispatch([ + { type: "hydrated", messages: [], pending: [pending], sequence: 1 }, + { + type: "event", + event: decodeEvent({ + id: "evt_discarded", + type: "session.next.prompt.discarded", + durable: { aggregateID: "ses_test", seq: 2, version: 1 }, + data: { sessionID: "ses_test", messageID: "msg_steer", timestamp: 2 }, + }), + }, + { type: "newest-merged", messages: [], pending: [pending], sequence: 1, hasOlder: false }, + ]) + expect(state.pending).toEqual([]) + }) + test("hydrates a chronological native projection and preserves prepared provider context", () => { const state = dispatch([ { diff --git a/packages/app/src/pages/session/current/reducer.ts b/packages/app/src/pages/session/current/reducer.ts index fb09e056ea16..e446043943f5 100644 --- a/packages/app/src/pages/session/current/reducer.ts +++ b/packages/app/src/pages/session/current/reducer.ts @@ -27,6 +27,7 @@ export type CurrentSessionAction = | { readonly type: "hydrated" readonly messages: ReadonlyArray + readonly pending?: ReadonlyArray readonly sequence?: number readonly cursor?: string } @@ -39,6 +40,8 @@ export type CurrentSessionAction = | { readonly type: "newest-merged" readonly messages: ReadonlyArray + readonly pending?: ReadonlyArray + readonly sequence?: number readonly hasOlder: boolean } | { readonly type: "event"; readonly event: SessionEvent.Event } @@ -73,7 +76,7 @@ export function reduceCurrentSession(state: CurrentSessionState, action: Current return { ...state, messages: [...action.messages], - pending: reconcilePending(state.pending, action.messages), + pending: reconcilePending(action.pending ?? state.pending, action.messages), readiness: "ready" as const, cursor: action.cursor, hasOlder: action.cursor !== undefined, @@ -104,7 +107,12 @@ export function reduceCurrentSession(state: CurrentSessionState, action: Current return { ...state, messages: Array.from(messages.values()).toSorted((left, right) => order(left).localeCompare(order(right))), - pending: reconcilePending(state.pending, action.messages), + pending: reconcilePending( + action.sequence !== undefined && action.sequence >= (state.lastEventSequence ?? -1) + ? (action.pending ?? state.pending) + : state.pending, + action.messages, + ), } } case "message-replaced": { diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index 440ee148f730..c1f20e9db8f7 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -355,7 +355,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { await runCommand({ owner, prompt: promptSession, - request: () => session.revert.stage({ sessionID, messageID: message.id }), + request: () => session.revert.stage({ sessionID, messageID: message.id, inclusive: true }), updatePrompt: (promptSession) => { if (parts) promptSession.set(extractPromptFromParts(parts, { directory })) }, @@ -391,7 +391,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { await runCommand({ owner, prompt: promptSession, - request: () => session.revert.stage({ sessionID, messageID: next.id }), + request: () => session.revert.stage({ sessionID, messageID: next.id, inclusive: true }), updatePrompt: () => undefined, updateViewport: () => setActiveMessage(messages[boundary]), }) diff --git a/packages/app/src/utils/server-compat.test.ts b/packages/app/src/utils/server-compat.test.ts index 80d67455a621..df2af0898609 100644 --- a/packages/app/src/utils/server-compat.test.ts +++ b/packages/app/src/utils/server-compat.test.ts @@ -6,6 +6,7 @@ function setup( protocol: "v1" | "v2" | Promise<"v1" | "v2">, responses?: { vcs?: { branch: string; default_branch: string } + vcsDiff?: Array<{ file: string; additions: number; deletions: number; patch: string }> mcpResource?: { location: { directory: string; project: { id: string; directory: string } }; data: unknown } currentQuestion?: "missing" }, @@ -39,6 +40,8 @@ function setup( delivery: "steer", }) } + if (request.method === "GET" && new URL(request.url).pathname === "/vcs/diff") + return Response.json(responses?.vcsDiff ?? []) if (request.method === "GET" && new URL(request.url).pathname === "/vcs") return Response.json(responses?.vcs ?? {}) if (request.method === "GET" && new URL(request.url).pathname === "/api/mcp/resource") @@ -71,6 +74,17 @@ function setup( } describe("createCompatibleApi", () => { + test("loads V2 review diffs from the supported compatibility route", async () => { + const file = { file: "a.ts", additions: 1, deletions: 1, patch: "patch" } + const { api, requests } = setup("v2", { vcsDiff: [file] }) + expect((await api.vcs.diff({ location: { directory: "/repo" }, mode: "working" })).data).toEqual([ + { ...file, status: "modified" }, + ]) + const url = new URL(requests[0]!.url) + expect(url.pathname).toBe("/vcs/diff") + expect(url.searchParams.get("mode")).toBe("git") + expect(url.searchParams.get("directory")).toBe("/repo") + }) /* test("routes V1 archive through the legacy session update", async () => { const { api, requests } = setup("v1") diff --git a/packages/app/src/utils/server-compat.ts b/packages/app/src/utils/server-compat.ts index 85cf7d11e953..24b177561291 100644 --- a/packages/app/src/utils/server-compat.ts +++ b/packages/app/src/utils/server-compat.ts @@ -19,7 +19,7 @@ import type { type LegacyClient = OpencodeClient type LegacyFor = (directory?: string) => LegacyClient type CompatibleSessionApi = Omit< - SessionApi, + ServerApi["session"], "prompt" | "command" | "shell" | "compact" | "rename" | "archive" | "remove" > & { prompt: (input: SessionPromptInput & LegacyPrompt) => Promise @@ -85,7 +85,8 @@ function sessionInfo(session: Session): SessionInfo { export function createCompatibleApi(input: CompatibleInput): CompatibleApi { const v1 = createV1Api(input) - const current = createCurrentApi(input.current, input.legacy) + // Current session support does not imply that the server implements /api/vcs. + const current = { ...createCurrentApi(input.current, input.legacy), vcs: v1.vcs } return lazyApi( input.protocol.then((protocol) => (protocol === "v1" ? v1 : current)), current, diff --git a/packages/app/src/utils/server-revert.ts b/packages/app/src/utils/server-revert.ts new file mode 100644 index 000000000000..5340f80e4d68 --- /dev/null +++ b/packages/app/src/utils/server-revert.ts @@ -0,0 +1,31 @@ +import type { OpenCodeClient } from "@opencode-ai/client/promise" +import type { OpencodeClient } from "@opencode-ai/sdk/v2/client" + +export type ReplacementApi = Omit & { + session: Omit & { + revert: Omit & { + stage: ( + input: Parameters[0] & { inclusive?: boolean }, + ) => ReturnType + } + } +} + +// The pinned promise client predates replacement mode. Use the generated SDK +// for this opt-in request until upstream exposes the field in that client. +export function withReplacementRevert(api: OpenCodeClient, sdk: OpencodeClient): ReplacementApi { + return { + ...api, + session: { + ...api.session, + revert: { + ...api.session.revert, + stage: async (input: Parameters[0] & { inclusive?: boolean }) => { + if (!input.inclusive) return api.session.revert.stage(input) + const result = await sdk.v2.session.revert.stage(input, { throwOnError: true }) + return { ...result.data.data, files: result.data.data.files?.map((file) => ({ ...file, file: file.path })) } + }, + }, + }, + } +} diff --git a/packages/app/src/utils/server.test.ts b/packages/app/src/utils/server.test.ts index 4666b7d6d03c..fee97abfc100 100644 --- a/packages/app/src/utils/server.test.ts +++ b/packages/app/src/utils/server.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { authFromToken, authTokenFromCredentials } from "./server" +import { authFromToken, authTokenFromCredentials, createApiForServer } from "./server" describe("authFromToken", () => { test("decodes basic auth credentials from auth_token", () => { @@ -21,3 +21,27 @@ describe("authTokenFromCredentials", () => { expect(authTokenFromCredentials({ password: "secret" })).toBe(btoa("opencode:secret")) }) }) + +describe("replacement revert transport", () => { + test("sends opt-in replacement through the generated SDK with server authentication", async () => { + const requests: Request[] = [] + const fetcher = Object.assign( + async (input: string | URL | Request, init?: RequestInit) => { + const request = new Request(input, init) + requests.push(request) + return Response.json({ + data: { + messageID: "msg_original", + files: [{ path: "a.ts", status: "modified", additions: 1, deletions: 1, patch: "patch" }], + }, + }) + }, + { preconnect: () => {} }, + ) + const api = createApiForServer({ server: { url: "http://localhost:4096", password: "test-only" }, fetch: fetcher }) + const result = await api.session.revert.stage({ sessionID: "ses_test", messageID: "msg_original", inclusive: true }) + expect(requests[0]?.headers.get("authorization")).toBe(`Basic ${btoa("opencode:test-only")}`) + expect(await requests[0]?.json()).toEqual({ messageID: "msg_original", inclusive: true }) + expect(result.files?.[0]?.file).toBe("a.ts") + }) +}) diff --git a/packages/app/src/utils/server.ts b/packages/app/src/utils/server.ts index e3264cee22ab..47e574c5cd46 100644 --- a/packages/app/src/utils/server.ts +++ b/packages/app/src/utils/server.ts @@ -1,5 +1,6 @@ import { createOpencodeClient } from "@opencode-ai/sdk/v2/client" -import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise" +import { withReplacementRevert } from "./server-revert" +import { OpenCode } from "@opencode-ai/client/promise" import type { ServerConnection } from "@/context/server" import { decode64 } from "@/utils/base64" @@ -51,23 +52,23 @@ export function createSdkForServer({ }) } -export function createApiForServer(input: { - server: ServerConnection.HttpBase - fetch?: typeof globalThis.fetch -}): OpenCodeClient { - return OpenCode.make({ - baseUrl: input.server.url, - fetch: input.fetch, - headers: input.server.password - ? { - Authorization: `Basic ${authTokenFromCredentials({ - username: input.server.username, - password: input.server.password, - })}`, - } - : undefined, - }) +export function createApiForServer(input: { server: ServerConnection.HttpBase; fetch?: typeof globalThis.fetch }) { + return withReplacementRevert( + OpenCode.make({ + baseUrl: input.server.url, + fetch: input.fetch, + headers: input.server.password + ? { + Authorization: `Basic ${authTokenFromCredentials({ + username: input.server.username, + password: input.server.password, + })}`, + } + : undefined, + }), + createSdkForServer(input), + ) } export { createCurrentClientForServer } from "./current-client" -export type ServerApi = OpenCodeClient +export type ServerApi = ReturnType diff --git a/packages/client/src/generated-effect/client.ts b/packages/client/src/generated-effect/client.ts index a397c5f81c1b..a1ef25e38acd 100644 --- a/packages/client/src/generated-effect/client.ts +++ b/packages/client/src/generated-effect/client.ts @@ -301,11 +301,12 @@ type Endpoint3_23Input = { readonly sessionID: Endpoint3_23Request["params"]["sessionID"] readonly messageID: Endpoint3_23Request["payload"]["messageID"] readonly files?: Endpoint3_23Request["payload"]["files"] + readonly inclusive?: Endpoint3_23Request["payload"]["inclusive"] } const Endpoint3_23 = (raw: RawClient["server.session"]) => (input: Endpoint3_23Input) => raw["session.revert.stage"]({ params: { sessionID: input["sessionID"] }, - payload: { messageID: input["messageID"], files: input["files"] }, + payload: { messageID: input["messageID"], files: input["files"], inclusive: input["inclusive"] }, }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), diff --git a/packages/client/src/generated/client.ts b/packages/client/src/generated/client.ts index cf4e04a186e6..47586f205b54 100644 --- a/packages/client/src/generated/client.ts +++ b/packages/client/src/generated/client.ts @@ -630,7 +630,7 @@ export function make(options: ClientOptions) { { method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`, - body: { messageID: input["messageID"], files: input["files"] }, + body: { messageID: input["messageID"], files: input["files"], inclusive: input["inclusive"] }, successStatus: 200, declaredStatuses: [404, 500, 400, 401], empty: false, diff --git a/packages/client/src/generated/types.ts b/packages/client/src/generated/types.ts index 3a04f7d2c885..ca581b2cdb7f 100644 --- a/packages/client/src/generated/types.ts +++ b/packages/client/src/generated/types.ts @@ -279,6 +279,8 @@ export type SessionsListOutput = { readonly subpath?: string readonly revert?: { readonly messageID: string + readonly inclusive?: boolean + readonly inputThroughSeq?: number readonly partID?: string readonly snapshot?: string readonly diff?: string @@ -354,6 +356,8 @@ export type SessionsCreateOutput = { readonly subpath?: string readonly revert?: { readonly messageID: string + readonly inclusive?: boolean + readonly inputThroughSeq?: number readonly partID?: string readonly snapshot?: string readonly diff?: string @@ -405,6 +409,8 @@ export type SessionsGetOutput = { readonly subpath?: string readonly revert?: { readonly messageID: string + readonly inclusive?: boolean + readonly inputThroughSeq?: number readonly partID?: string readonly snapshot?: string readonly diff?: string @@ -457,6 +463,8 @@ export type SessionsUpdateOutput = { readonly subpath?: string readonly revert?: { readonly messageID: string + readonly inclusive?: boolean + readonly inputThroughSeq?: number readonly partID?: string readonly snapshot?: string readonly diff?: string @@ -509,6 +517,8 @@ export type SessionsForkOutput = { readonly subpath?: string readonly revert?: { readonly messageID: string + readonly inclusive?: boolean + readonly inputThroughSeq?: number readonly partID?: string readonly snapshot?: string readonly diff?: string @@ -558,6 +568,8 @@ export type SessionsShareOutput = { readonly subpath?: string readonly revert?: { readonly messageID: string + readonly inclusive?: boolean + readonly inputThroughSeq?: number readonly partID?: string readonly snapshot?: string readonly diff?: string @@ -607,6 +619,8 @@ export type SessionsUnshareOutput = { readonly subpath?: string readonly revert?: { readonly messageID: string + readonly inclusive?: boolean + readonly inputThroughSeq?: number readonly partID?: string readonly snapshot?: string readonly diff?: string @@ -2623,13 +2637,28 @@ export type SessionsWaitOutput = void export type SessionsStageInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] - readonly messageID: { readonly messageID: string; readonly files?: boolean | undefined }["messageID"] - readonly files?: { readonly messageID: string; readonly files?: boolean | undefined }["files"] + readonly messageID: { + readonly messageID: string + readonly files?: boolean | undefined + readonly inclusive?: boolean | undefined + }["messageID"] + readonly files?: { + readonly messageID: string + readonly files?: boolean | undefined + readonly inclusive?: boolean | undefined + }["files"] + readonly inclusive?: { + readonly messageID: string + readonly files?: boolean | undefined + readonly inclusive?: boolean | undefined + }["inclusive"] } export type SessionsStageOutput = { readonly data: { readonly messageID: string + readonly inclusive?: boolean + readonly inputThroughSeq?: number readonly partID?: string readonly snapshot?: string readonly diff?: string @@ -3988,6 +4017,8 @@ export type SessionsHistoryOutput = { readonly sessionID: string readonly revert: { readonly messageID: string + readonly inclusive?: boolean + readonly inputThroughSeq?: number readonly partID?: string readonly snapshot?: string readonly diff?: string @@ -5110,6 +5141,8 @@ export type SessionsEventsOutput = readonly sessionID: string readonly revert: { readonly messageID: string + readonly inclusive?: boolean + readonly inputThroughSeq?: number readonly partID?: string readonly snapshot?: string readonly diff?: string @@ -5653,6 +5686,99 @@ export type MessagesListOutput = { readonly time: { readonly created: number } } > + readonly pending?: ReadonlyArray<{ + readonly id: string + readonly metadata?: { readonly [x: string]: JsonValue } + readonly time: { readonly created: number } + readonly text: string + readonly files?: ReadonlyArray<{ + readonly uri: string + readonly mime: string + readonly name?: string + readonly description?: string + readonly source?: { readonly start: number; readonly end: number; readonly text: string } + }> + readonly agents?: ReadonlyArray<{ + readonly name: string + readonly source?: { readonly start: number; readonly end: number; readonly text: string } + }> + readonly payload?: { + readonly version: 1 + readonly agent: string + readonly model: { readonly providerID: string; readonly modelID: string; readonly variant?: string } + readonly tools?: { readonly [x: string]: boolean } + readonly system?: string + readonly format?: + | { readonly type: "text" } + | { + readonly type: "json_schema" + readonly schema: { readonly [x: string]: JsonValue } + readonly retryCount?: number | null | null + } + readonly parts: ReadonlyArray< + | { + readonly id?: string + readonly type: "text" + readonly text: string + readonly synthetic?: boolean + readonly ignored?: boolean + readonly time?: { readonly start: number; readonly end?: number } + readonly metadata?: { readonly [x: string]: JsonValue } + } + | { + readonly id?: string + readonly type: "file" + readonly mime: string + readonly filename?: string + readonly url: string + readonly source?: + | { + readonly type: "file" + readonly path: string + readonly text: { readonly value: string; readonly start: number; readonly end: number } + } + | { + readonly type: "symbol" + readonly path: string + readonly range: { + readonly start: { readonly line: number; readonly character: number } + readonly end: { readonly line: number; readonly character: number } + } + readonly name: string + readonly kind: number + readonly text: { readonly value: string; readonly start: number; readonly end: number } + } + | { + readonly type: "resource" + readonly clientName: string + readonly uri: string + readonly text: { readonly value: string; readonly start: number; readonly end: number } + } + } + | { + readonly id?: string + readonly type: "agent" + readonly name: string + readonly source?: { readonly value: string; readonly start: number; readonly end: number } + } + | { + readonly id?: string + readonly type: "subtask" + readonly prompt: string + readonly description: string + readonly agent: string + readonly model?: { readonly providerID: string; readonly modelID: string; readonly variant?: string } + readonly command?: string + } + > + readonly permissions?: ReadonlyArray<{ + readonly permission: string + readonly pattern: string + readonly action: "allow" | "deny" | "ask" + }> + } + readonly type: "user" + }> | null readonly throughSeq: number readonly cursor: { readonly previous?: string | null; readonly next?: string | null } } diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 2b728ade0d82..981f833eb2b2 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -250,6 +250,7 @@ export interface Interface { sessionID: SessionSchema.ID messageID: SessionMessage.ID files?: boolean + inclusive?: boolean }) => Effect.Effect readonly clear: (sessionID: SessionSchema.ID) => Effect.Effect readonly commit: (sessionID: SessionSchema.ID) => Effect.Effect @@ -942,7 +943,12 @@ const layer = Layer.effect( revert: { stage: Effect.fn("V2Session.revert.stage")(function* (input) { const session = yield* result.get(input.sessionID) - return yield* SessionRevert.stage({ session, messageID: input.messageID, files: input.files }).pipe( + return yield* SessionRevert.stage({ + session, + messageID: input.messageID, + files: input.files, + inclusive: input.inclusive, + }).pipe( Effect.provideService(Database.Service, database), Effect.provideService(EventV2.Service, events), Effect.provide(locations.get(session.location)), diff --git a/packages/core/src/session/input.ts b/packages/core/src/session/input.ts index 4e7320c70482..d83e9bfa47b5 100644 --- a/packages/core/src/session/input.ts +++ b/packages/core/src/session/input.ts @@ -40,6 +40,27 @@ export const find = Effect.fn("SessionInput.find")(function* (db: DatabaseServic return row === undefined ? undefined : fromRow(row) }) +export const listPending = Effect.fn("SessionInput.listPending")(function* ( + db: DatabaseService, + sessionID: SessionSchema.ID, +) { + const rows = yield* db + .select() + .from(SessionInputTable) + .where( + and( + eq(SessionInputTable.session_id, sessionID), + eq(SessionInputTable.delivery, "steer"), + isNull(SessionInputTable.promoted_seq), + isNull(SessionInputTable.discarded_seq), + ), + ) + .orderBy(asc(SessionInputTable.admitted_seq)) + .all() + .pipe(Effect.orDie) + return rows.map(fromRow) +}) + export class LifecycleConflict extends Schema.TaggedErrorClass()("SessionInput.LifecycleConflict", { id: SessionMessage.ID, }) {} diff --git a/packages/core/src/session/projector.ts b/packages/core/src/session/projector.ts index f848748ffce4..49dc7015c008 100644 --- a/packages/core/src/session/projector.ts +++ b/packages/core/src/session/projector.ts @@ -11,6 +11,7 @@ import { WorkspaceTable } from "../control-plane/workspace.sql" import { SessionMessage } from "./message" import { SessionMessageUpdater } from "./message-updater" import { SessionInput } from "./input" +import { commitReplacement } from "./revert-replacement" import { WorkspaceV2 } from "../workspace" import { MessageTable, PartTable, SessionInputTable, SessionMessageTable, SessionTable } from "./sql" import type { DeepMutable } from "../schema" @@ -458,6 +459,22 @@ const layer = Layer.effectDiscard( ) yield* events.project(SessionEvent.RevertEvent.Committed, (event) => Effect.gen(function* () { + const session = yield* db + .select({ revert: SessionTable.revert }) + .from(SessionTable) + .where(eq(SessionTable.id, event.data.sessionID)) + .get() + .pipe(Effect.orDie) + if (session?.revert?.inclusive) { + yield* commitReplacement(db, event.data.sessionID, session.revert) + yield* db + .update(SessionTable) + .set({ revert: null, time_updated: DateTime.toEpochMillis(event.data.timestamp) }) + .where(eq(SessionTable.id, event.data.sessionID)) + .run() + .pipe(Effect.orDie) + return + } const boundary = yield* db .select({ seq: SessionMessageTable.seq }) .from(SessionMessageTable) diff --git a/packages/core/src/session/revert-replacement.ts b/packages/core/src/session/revert-replacement.ts new file mode 100644 index 000000000000..0882cedaff0b --- /dev/null +++ b/packages/core/src/session/revert-replacement.ts @@ -0,0 +1,52 @@ +import { and, eq, gte, isNull, lte, or } from "drizzle-orm" +import { Effect } from "effect" +import type { Database } from "../database/database" +import type { SessionSchema } from "./schema" +import { SessionInputTable, SessionMessageTable } from "./sql" + +// Replacement is opt-in; the upstream revert contract retains its boundary. +export const commitReplacement = Effect.fn("SessionRevert.commitReplacement")(function* ( + db: Database.Interface["db"], + sessionID: SessionSchema.ID, + revert: NonNullable, +) { + const message = yield* db + .select({ seq: SessionMessageTable.seq }) + .from(SessionMessageTable) + .where(and(eq(SessionMessageTable.session_id, sessionID), eq(SessionMessageTable.id, revert.messageID))) + .get() + .pipe(Effect.orDie) + const input = yield* db + .select() + .from(SessionInputTable) + .where(and(eq(SessionInputTable.session_id, sessionID), eq(SessionInputTable.id, revert.messageID))) + .get() + .pipe(Effect.orDie) + if (!message && (!input || input.delivery !== "steer" || input.promoted_seq !== null || input.discarded_seq !== null)) + return yield* Effect.die(`Replacement boundary not found: ${revert.messageID}`) + if (message) + yield* db + .delete(SessionMessageTable) + .where(and(eq(SessionMessageTable.session_id, sessionID), gte(SessionMessageTable.seq, message.seq))) + .run() + .pipe(Effect.orDie) + yield* db + .delete(SessionInputTable) + .where( + and( + eq(SessionInputTable.session_id, sessionID), + or( + message ? gte(SessionInputTable.promoted_seq, message.seq) : undefined, + and( + eq(SessionInputTable.delivery, "steer"), + isNull(SessionInputTable.promoted_seq), + gte(SessionInputTable.admitted_seq, input?.admitted_seq ?? message!.seq), + // Do not discard the replacement admitted after staging, or explicit queues. + lte(SessionInputTable.admitted_seq, revert.inputThroughSeq ?? 0), + ), + ), + ), + ) + .run() + .pipe(Effect.orDie) +}) diff --git a/packages/core/src/session/revert.ts b/packages/core/src/session/revert.ts index 9999d5da5a04..595e14342385 100644 --- a/packages/core/src/session/revert.ts +++ b/packages/core/src/session/revert.ts @@ -7,6 +7,7 @@ import { EventV2 } from "../event" import { RelativePath } from "../schema" import { Snapshot } from "../snapshot" import { SessionEvent } from "./event" +import { SessionInput } from "./input" import { SessionMessage } from "./message" import { SessionSchema } from "./schema" import { SessionMessageTable } from "./sql" @@ -22,6 +23,7 @@ export class MessageNotFoundError extends Schema.TaggedErrorClass() + return yield* new MessageNotFoundError(input) + } const rows = yield* db .select() .from(SessionMessageTable) @@ -61,13 +73,16 @@ export const stage = Effect.fn("SessionRevert.stage")(function* (input: { readonly session: SessionSchema.Info readonly messageID: SessionMessage.ID readonly files?: boolean + readonly inclusive?: boolean }) { + const db = (yield* Database.Service).db + const inputThroughSeq = input.inclusive ? yield* EventV2.latestSequence(db, input.session.id) : undefined const snapshot = yield* Snapshot.Service const events = yield* EventV2.Service const original = input.session.revert?.snapshot ? Snapshot.ID.make(input.session.revert.snapshot) : yield* snapshot.capture() - const next = yield* plan({ sessionID: input.session.id, messageID: input.messageID }) + const next = yield* plan({ sessionID: input.session.id, messageID: input.messageID, inclusive: input.inclusive }) const restore = new Map() if (original) { for (const file of input.session.revert?.files ?? []) restore.set(file.path, original) @@ -80,6 +95,8 @@ export const stage = Effect.fn("SessionRevert.stage")(function* (input: { : [] const revert = { messageID: input.messageID, + inclusive: input.inclusive, + inputThroughSeq, snapshot: original, diff: files .map((file) => file.patch) diff --git a/packages/core/test/session-prompt.test.ts b/packages/core/test/session-prompt.test.ts index 822ad497c2f2..b169bbd5023b 100644 --- a/packages/core/test/session-prompt.test.ts +++ b/packages/core/test/session-prompt.test.ts @@ -198,6 +198,43 @@ describe("SessionV2.prompt", () => { }), ) + it.effect("pending UI inputs exclude queued, discarded and promoted messages", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + const database = yield* Database.Service + const events = yield* EventV2.Service + const steer = yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "keep visible" }), resume: false }) + const removed = yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "discard me" }), resume: false }) + yield* session.prompt({ + sessionID, + prompt: Prompt.make({ text: "queue separately" }), + delivery: "queue", + resume: false, + }) + expect((yield* SessionInput.listPending(database.db, sessionID)).map((input) => input.id)).toEqual([ + steer.id, + removed.id, + ]) + yield* session.interrupt(sessionID) + expect((yield* SessionInput.listPending(database.db, sessionID)).map((input) => input.id)).toEqual([ + steer.id, + removed.id, + ]) + yield* database.db + .update(SessionInputTable) + .set({ discarded_seq: 99 }) + .where(eq(SessionInputTable.id, removed.id)) + .run() + .pipe(Effect.orDie) + expect((yield* SessionInput.listPending(database.db, sessionID)).map((input) => input.id)).toEqual([steer.id]) + expect(yield* session.messages({ sessionID })).toEqual([]) + yield* SessionInput.promoteSteers(database.db, events, sessionID, Number.MAX_SAFE_INTEGER) + expect(yield* SessionInput.listPending(database.db, sessionID)).toEqual([]) + expect(yield* session.messages({ sessionID })).toMatchObject([{ id: steer.id, text: "keep visible" }]) + }), + ) + it.effect("durably admits one user message before transcript promotion", () => Effect.gen(function* () { yield* setup diff --git a/packages/core/test/session-replacement.test.ts b/packages/core/test/session-replacement.test.ts new file mode 100644 index 000000000000..7077eaf59620 --- /dev/null +++ b/packages/core/test/session-replacement.test.ts @@ -0,0 +1,110 @@ +import { expect } from "bun:test" +import { DateTime, Effect, Schema } from "effect" +import { Database } from "@opencode-ai/core/database/database" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { EventV2 } from "@opencode-ai/core/event" +import { Project } from "@opencode-ai/core/project" +import { ProjectTable } from "@opencode-ai/core/project/sql" +import { AbsolutePath } from "@opencode-ai/core/schema" +import { SessionV2 } from "@opencode-ai/core/session" +import { SessionEvent } from "@opencode-ai/core/session/event" +import { SessionMessage } from "@opencode-ai/core/session/message" +import { SessionProjector } from "@opencode-ai/core/session/projector" +import { SessionInputTable, SessionMessageTable, SessionTable } from "@opencode-ai/core/session/sql" +import { testEffect } from "./lib/effect" + +const it = testEffect(AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node, SessionProjector.node]))) + +for (const promoted of [true, false]) { + it.effect( + `replacement removes its ${promoted ? "promoted" : "pending"} boundary but preserves queues and new admission`, + () => + Effect.gen(function* () { + const database = yield* Database.Service + const events = yield* EventV2.Service + const sessionID = SessionV2.ID.make("ses_replacement") + const id = SessionMessage.ID.make("msg_original") + yield* database.db + .insert(ProjectTable) + .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) + .run() + yield* database.db + .insert(SessionTable) + .values({ + id: sessionID, + project_id: Project.ID.global, + slug: "test", + directory: "/project", + title: "test", + version: "test", + }) + .run() + yield* database.db + .insert(SessionInputTable) + .values([ + { + id, + session_id: sessionID, + prompt: { text: "original" }, + delivery: "steer", + admitted_seq: 1, + promoted_seq: promoted ? 2 : null, + }, + { + id: SessionMessage.ID.make("msg_later"), + session_id: sessionID, + prompt: { text: "later steer" }, + delivery: "steer", + admitted_seq: 3, + }, + { + id: SessionMessage.ID.make("msg_queue"), + session_id: sessionID, + prompt: { text: "queue" }, + delivery: "queue", + admitted_seq: 4, + }, + { + id: SessionMessage.ID.make("msg_replacement"), + session_id: sessionID, + prompt: { text: "replacement" }, + delivery: "steer", + admitted_seq: 6, + }, + ]) + .run() + if (promoted) + yield* database.db + .insert(SessionMessageTable) + .values({ + id, + session_id: sessionID, + seq: 2, + type: "user", + time_created: 1, + data: Schema.encodeSync(SessionMessage.User)( + SessionMessage.User.make({ + id, + type: "user", + text: "original", + time: { created: DateTime.makeUnsafe(1) }, + }), + ), + }) + .run() + const revert = { messageID: id, inclusive: true, inputThroughSeq: 4 } + yield* events.publish(SessionEvent.RevertEvent.Staged, { sessionID, timestamp: DateTime.makeUnsafe(1), revert }) + expect((yield* database.db.select().from(SessionInputTable).all()).length).toBe(4) + yield* events.publish(SessionEvent.RevertEvent.Committed, { + sessionID, + messageID: id, + timestamp: DateTime.makeUnsafe(2), + }) + expect(yield* database.db.select().from(SessionMessageTable).all()).toEqual([]) + expect((yield* database.db.select().from(SessionInputTable).all()).map((row) => String(row.id)).sort()).toEqual( + ["msg_queue", "msg_replacement"], + ) + }), + ) +} diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 8e1db92e0bce..6fb5c59d1461 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -1726,6 +1726,54 @@ describe("SessionRunnerLLM", () => { }), ) + it.effect("replaces a staged user prompt before the next provider turn without repeating it", () => + Effect.gen(function* () { + yield* setup + const session = yield* SessionV2.Service + const events = yield* EventV2.Service + const boundary = SessionMessage.ID.create() + yield* session.prompt({ + id: boundary, + sessionID, + prompt: Prompt.make({ text: "boundary" }), + resume: false, + }) + response = [] + yield* session.resume(sessionID) + yield* events.publish(SessionEvent.RevertEvent.Staged, { + sessionID, + timestamp: DateTime.makeUnsafe(1), + revert: { + messageID: boundary, + files: [], + inclusive: true, + inputThroughSeq: yield* session.latestSequence(sessionID), + }, + }) + const steer = yield* session.prompt({ + sessionID, + prompt: Prompt.make({ text: "steer after revert" }), + resume: false, + }) + response = fragmentFixture("text", "text-after-steer-revert", ["done"]).completeEvents + + yield* session.resume(sessionID) + + expect(yield* session.context(sessionID)).toMatchObject([ + { id: steer.id, type: "user", text: "steer after revert" }, + { type: "assistant", content: [{ type: "text", text: "done" }] }, + ]) + const history = yield* session.history({ sessionID, limit: 100 }) + const committed = history.events.findIndex((event) => event.type === "session.next.revert.committed") + const promoted = history.events.findIndex( + (event) => + event.type === "session.next.prompted" && "messageID" in event.data && event.data.messageID === steer.id, + ) + expect(committed).toBeGreaterThan(-1) + expect(promoted).toBeGreaterThan(committed) + }), + ) + it.effect("projects a payload-controlled queued tool continuation into the consumer timeline and history", () => Effect.gen(function* () { yield* setup diff --git a/packages/opencode/test/server/httpapi-exercise/index.ts b/packages/opencode/test/server/httpapi-exercise/index.ts index 6516a5d07c27..57e81624deca 100644 --- a/packages/opencode/test/server/httpapi-exercise/index.ts +++ b/packages/opencode/test/server/httpapi-exercise/index.ts @@ -176,7 +176,7 @@ const scenarios: Scenario[] = [ .status(400), http.protected.get("/config/providers", "config.providers").json(), http.protected.get("/project", "project.list").json(200, array, "status"), - http.protected.get("/api/project", "v2.project.list").json(200, array, "status"), + http.protected.get("/api/project", "v2.project.list").json(200, locationData(array), "status"), http.protected.get("/project/current", "project.current").json( 200, (body, ctx) => { @@ -189,7 +189,10 @@ const scenarios: Scenario[] = [ 200, (body, ctx) => { object(body) - check(body.worktree === ctx.directory, "current project should resolve from scenario directory") + object(body.location) + object(body.location.project) + object(body.data) + check(body.data.worktree === ctx.directory, "current project should resolve from scenario directory") }, "status", ), @@ -251,7 +254,7 @@ const scenarios: Scenario[] = [ path: route("/api/project/{projectID}/directories", { projectID: ctx.state.id }), headers: ctx.headers(), })) - .json(200, array, "status"), + .json(200, locationData(array), "status"), http.protected .post("/experimental/project/{projectID}/copy/generate-name", "experimental.projectCopy.generateName") .seeded((ctx) => ctx.project()) diff --git a/packages/protocol/src/groups/message.ts b/packages/protocol/src/groups/message.ts index c198016edbf4..877937ac411c 100644 --- a/packages/protocol/src/groups/message.ts +++ b/packages/protocol/src/groups/message.ts @@ -29,6 +29,7 @@ export const MessageGroup = HttpApiGroup.make("server.message") query: SessionMessagesQuery, success: Schema.Struct({ data: Schema.Array(SessionMessage.Message), + pending: Schema.optional(Schema.Array(SessionMessage.User)), throughSeq: NonNegativeInt, cursor: Schema.Struct({ previous: Schema.String.pipe(Schema.optional), diff --git a/packages/protocol/src/groups/session.ts b/packages/protocol/src/groups/session.ts index d91d495f5869..4d539c3c3118 100644 --- a/packages/protocol/src/groups/session.ts +++ b/packages/protocol/src/groups/session.ts @@ -432,7 +432,11 @@ export const makeSessionGroup = (sessionLo .add( HttpApiEndpoint.post("session.revert.stage", "/api/session/:sessionID/revert/stage", { params: { sessionID: Session.ID }, - payload: Schema.Struct({ messageID: SessionMessage.ID, files: Schema.Boolean.pipe(Schema.optional) }), + payload: Schema.Struct({ + messageID: SessionMessage.ID, + files: Schema.Boolean.pipe(Schema.optional), + inclusive: Schema.Boolean.pipe(Schema.optional), + }), success: Schema.Struct({ data: Revert.State }), error: [MessageNotFoundError, SessionNotFoundError, UnknownError], }) diff --git a/packages/schema/src/revert.ts b/packages/schema/src/revert.ts index 05222d539819..dc73d5a68aa3 100644 --- a/packages/schema/src/revert.ts +++ b/packages/schema/src/revert.ts @@ -16,6 +16,8 @@ export interface FileDiff extends Schema.Schema.Type {} export const State = Schema.Struct({ messageID: SessionMessage.ID, + inclusive: Schema.Boolean.pipe(optional), + inputThroughSeq: NonNegativeInt.pipe(optional), partID: Schema.String.pipe(optional), snapshot: Schema.String.pipe(optional), diff: Schema.String.pipe(optional), diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index d5a5d60ac5fe..5587e7b775ae 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -3169,6 +3169,7 @@ export class Revert extends HeyApiClient { sessionID: string messageID?: string files?: boolean + inclusive?: boolean }, options?: Options, ) { @@ -3180,6 +3181,7 @@ export class Revert extends HeyApiClient { { in: "path", key: "sessionID" }, { in: "body", key: "messageID" }, { in: "body", key: "files" }, + { in: "body", key: "inclusive" }, ], }, ], diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index eb343970b9f7..cc675c0d4b93 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2909,6 +2909,7 @@ export type SessionDurableEventStream = string export type SessionMessagesResponse = { data: Array + pending?: Array throughSeq: number cursor: { previous?: string @@ -3595,6 +3596,8 @@ export type FileDiff = { export type RevertState = { messageID: string + inclusive?: boolean + inputThroughSeq?: number partID?: string snapshot?: string diff?: string @@ -12969,6 +12972,7 @@ export type V2SessionRevertStageData = { body: { messageID: string files?: boolean + inclusive?: boolean } path: { sessionID: string diff --git a/packages/server/src/handlers/message.ts b/packages/server/src/handlers/message.ts index 358ad808c377..cc5bf3718e9c 100644 --- a/packages/server/src/handlers/message.ts +++ b/packages/server/src/handlers/message.ts @@ -1,10 +1,13 @@ import { SessionMessage } from "@opencode-ai/core/session/message" import { SessionV2 } from "@opencode-ai/core/session" +import { SessionInput } from "@opencode-ai/core/session/input" +import { Database } from "@opencode-ai/core/database/database" import { Effect, Schema } from "effect" import { HttpApiBuilder } from "effect/unstable/httpapi" import { Api } from "../api" import { InvalidCursorError, SessionNotFoundError, UnknownError } from "@opencode-ai/protocol/errors" import { firstPreparedMessageID, stripRepeatedPreparedContext } from "./prepared-context" +import { pendingInputMessages } from "./pending-inputs" const DefaultMessagesLimit = 50 @@ -28,6 +31,7 @@ const cursor = { export const MessageHandler = HttpApiBuilder.group(Api, "server.message", (handlers) => Effect.gen(function* () { const session = yield* SessionV2.Service + const database = yield* Database.Service return handlers.handle( "session.messages", @@ -88,8 +92,10 @@ export const MessageHandler = HttpApiBuilder.group(Api, "server.message", (handl const projected = messages.map((message) => stripRepeatedPreparedContext(message, firstPreparedID)) const first = projected[0] const last = projected.at(-1) + const pending = pendingInputMessages(yield* SessionInput.listPending(database.db, ctx.params.sessionID)) return { data: projected, + pending, throughSeq, cursor: { previous: first ? cursor.encode(first, order, "previous") : undefined, diff --git a/packages/server/src/handlers/pending-inputs.ts b/packages/server/src/handlers/pending-inputs.ts new file mode 100644 index 000000000000..741a6fb1da19 --- /dev/null +++ b/packages/server/src/handlers/pending-inputs.ts @@ -0,0 +1,17 @@ +import { SessionInput } from "@opencode-ai/core/session/input" +import { SessionMessage } from "@opencode-ai/core/session/message" + +// This is a UI projection only. The runner still owns transcript promotion. +export function pendingInputMessages(inputs: readonly SessionInput.Admitted[]) { + return inputs.map((input) => + SessionMessage.User.make({ + id: input.id, + type: "user", + text: input.prompt.text, + files: input.prompt.files, + agents: input.prompt.agents, + payload: input.payload, + time: { created: input.timeCreated }, + }), + ) +} diff --git a/packages/server/test/pending-inputs.test.ts b/packages/server/test/pending-inputs.test.ts new file mode 100644 index 000000000000..c035f8fc2229 --- /dev/null +++ b/packages/server/test/pending-inputs.test.ts @@ -0,0 +1,19 @@ +import { expect, test } from "bun:test" +import { SessionInput } from "@opencode-ai/core/session/input" +import { Schema } from "effect" +import { pendingInputMessages } from "../src/handlers/pending-inputs" + +test("projects admitted inputs without dropping prompt content or changing their identity", () => { + const input = Schema.decodeUnknownSync(SessionInput.Admitted)({ + id: "msg_pending", + sessionID: "ses_pending", + admittedSeq: 3, + prompt: { text: "Preserve this", files: [{ uri: "data:text/plain;base64,aGk=", mime: "text/plain" }] }, + delivery: "steer", + timeCreated: 1, + }) + const [message] = pendingInputMessages([input]) + expect(message).toMatchObject({ id: input.id, type: "user", text: input.prompt.text, files: input.prompt.files }) + expect(message?.time.created).toEqual(input.timeCreated) + expect(pendingInputMessages([])).toEqual([]) +}) From 08754ca2322631e2042f29bbb2cb7ee59649a517 Mon Sep 17 00:00:00 2001 From: henry701 Date: Fri, 4 Sep 2026 23:52:13 -0300 Subject: [PATCH 017/129] chore(ci): gate fork releases and upstream sync with validation --- .github/workflows/test.yml | 5 +++-- .github/workflows/typecheck.yml | 10 +++++++--- .github/workflows/upstream-sync.yml | 30 +++++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9e49c96264ca..7542a65f23cc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,7 @@ on: push: branches: - dev + - production pull_request: workflow_dispatch: @@ -31,7 +32,7 @@ jobs: host: blacksmith-4vcpu-ubuntu-2404 - name: windows host: blacksmith-4vcpu-windows-2025 - runs-on: ${{ matrix.settings.host }} + runs-on: ${{ github.repository == 'anomalyco/opencode' && matrix.settings.host || (matrix.settings.name == 'windows' && 'windows-latest' || 'ubuntu-latest') }} defaults: run: shell: bash @@ -89,7 +90,7 @@ jobs: host: blacksmith-4vcpu-ubuntu-2404 - name: windows host: blacksmith-4vcpu-windows-2025 - runs-on: ${{ matrix.settings.host }} + runs-on: ${{ github.repository == 'anomalyco/opencode' && matrix.settings.host || (matrix.settings.name == 'windows' && 'windows-latest' || 'ubuntu-latest') }} env: PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers defaults: diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index fc9a52797c1d..1f8e95807dd5 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -2,14 +2,14 @@ name: typecheck on: push: - branches: [dev] + branches: [dev, production] pull_request: - branches: [dev] + branches: [dev, production] workflow_dispatch: jobs: typecheck: - runs-on: blacksmith-4vcpu-ubuntu-2404 + runs-on: ${{ github.repository == 'anomalyco/opencode' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 @@ -19,3 +19,7 @@ jobs: - name: Run typecheck run: bun typecheck + + - name: Typecheck browser regression fixtures + working-directory: packages/app + run: bun run typecheck:e2e diff --git a/.github/workflows/upstream-sync.yml b/.github/workflows/upstream-sync.yml index 11aed2d6ed07..402765b65cb5 100644 --- a/.github/workflows/upstream-sync.yml +++ b/.github/workflows/upstream-sync.yml @@ -2,11 +2,11 @@ name: Sync Upstream on: schedule: - - cron: '0 6 * * *' + - cron: "0 6 * * *" workflow_dispatch: inputs: force: - description: 'Force merge even if already up-to-date' + description: "Force merge even if already up-to-date" type: boolean default: false @@ -91,6 +91,32 @@ jobs: } >> "$GITHUB_OUTPUT" fi + # GITHUB_TOKEN pushes do not trigger the normal push workflows. Gate here. + - name: Setup validation tools + if: steps.merge.outputs.status == 'merged' + uses: ./.github/actions/setup-bun + + - name: Validate merged types and unit tests + if: steps.merge.outputs.status == 'merged' + run: | + bun turbo typecheck --concurrency=2 + bun --cwd packages/app run typecheck:e2e + GITHUB_ACTIONS=false bun turbo test --concurrency=2 + bun --cwd packages/client run check:generated + bun --cwd packages/opencode run test:httpapi + + - name: Install browser test dependencies + if: steps.merge.outputs.status == 'merged' + working-directory: packages/app + run: bunx playwright install --with-deps chromium firefox + + - name: Validate merged browser behavior + if: steps.merge.outputs.status == 'merged' + working-directory: packages/app + env: + CI: true + run: bun run test:e2e:local + - name: Push merged changes if: steps.merge.outputs.status == 'merged' shell: bash From 85a0a0e28ed3e3dedd23560c6ef34d3f1852c13f Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 00:13:06 -0300 Subject: [PATCH 018/129] fix(app): preserve keyboard tab menus and current server fixtures --- .../regression/cross-server-tab-close.spec.ts | 8 +-- .../remote-session-settings.spec.ts | 57 ++++++++++--------- .../e2e/regression/remote-tab-busy.spec.ts | 2 +- .../regression/review-line-comment.spec.ts | 4 +- .../app/e2e/regression/session-rename.spec.ts | 21 +------ .../regression/tab-navigate-mousedown.spec.ts | 5 +- packages/app/e2e/tsconfig.json | 7 ++- packages/app/e2e/utils/mock-server.ts | 10 +++- .../components/titlebar-tab-gesture.test.ts | 43 +++++++++++++- .../src/components/titlebar-tab-gesture.ts | 11 ++++ .../app/src/components/titlebar-tab-nav.tsx | 6 +- 11 files changed, 115 insertions(+), 59 deletions(-) diff --git a/packages/app/e2e/regression/cross-server-tab-close.spec.ts b/packages/app/e2e/regression/cross-server-tab-close.spec.ts index f09a2c7b63ae..0433043aef87 100644 --- a/packages/app/e2e/regression/cross-server-tab-close.spec.ts +++ b/packages/app/e2e/regression/cross-server-tab-close.spec.ts @@ -2,7 +2,7 @@ import { expect, test, type Page, type Route } from "@playwright/test" import { base64Encode } from "@opencode-ai/core/util/encode" import { currentSession } from "../utils/mock-server" -const serverA = "http://127.0.0.1:4096" +const serverA = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` const serverB = "http://127.0.0.1:4097" const sessionA = session("ses_server_a", "C:/server-a", "Server A session") const sessionB = session("ses_server_b", "/home/server-b", "Server B session") @@ -11,18 +11,18 @@ test("closing the active server's last tab opens the remaining server tab", asyn const requests: string[] = [] await mockServers(page, requests) await page.addInitScript( - ({ serverB, sessionA, sessionB }) => { + ({ serverA, serverB, sessionA, sessionB }) => { localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] })) localStorage.setItem( "opencode.window.browser.dat:tabs", JSON.stringify([ - { type: "session", server: "http://127.0.0.1:4096", sessionId: sessionA }, + { type: "session", server: serverA, sessionId: sessionA }, { type: "session", server: serverB, sessionId: sessionB }, ]), ) }, - { serverB, sessionA: sessionA.id, sessionB: sessionB.id }, + { serverA, serverB, sessionA: sessionA.id, sessionB: sessionB.id }, ) const hrefA = `/server/${base64Encode(serverA)}/session/${sessionA.id}` diff --git a/packages/app/e2e/regression/remote-session-settings.spec.ts b/packages/app/e2e/regression/remote-session-settings.spec.ts index b7e522a0ad0f..9aea55d4623f 100644 --- a/packages/app/e2e/regression/remote-session-settings.spec.ts +++ b/packages/app/e2e/regression/remote-session-settings.spec.ts @@ -1,9 +1,9 @@ import { base64Encode } from "@opencode-ai/core/util/encode" import { expect, test, type Page, type Route } from "@playwright/test" import { installSseTransport } from "../utils/sse-transport" -import { currentSession } from "../utils/mock-server" +import { currentCatalog, currentSession } from "../utils/mock-server" -const serverA = "http://127.0.0.1:4096" +const serverA = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` const serverB = "http://127.0.0.1:4097" const directoryA = "C:/server-a" const directoryB = "/home/server-b" @@ -32,7 +32,7 @@ test("session settings use the remote server context", async ({ page }) => { .poll(() => permissionRequests.some((request) => { const url = new URL(request) - return url.origin === serverB && url.searchParams.get("directory") === directoryB + return url.origin === serverB && url.searchParams.get("location[directory]") === directoryB }), ) .toBe(true) @@ -48,7 +48,7 @@ test("auto-accept responds for an unfocused server session", async ({ page }) => const permissionResponses: PermissionResponse[] = [] const transport = await installSseTransport<{ directory: string; payload: Record }>(page, { server: serverA, - path: "/global/event", + path: "/api/event", retry: 20, }) await mockServers(page, permissionRequests, permissionResponses) @@ -68,7 +68,7 @@ test("auto-accept responds for an unfocused server session", async ({ page }) => .poll(() => permissionRequests.some((request) => { const url = new URL(request) - return url.origin === serverA && url.searchParams.get("directory") === directoryA + return url.origin === serverA && url.searchParams.get("location[directory]") === directoryA }), ) .toBe(true) @@ -83,14 +83,14 @@ test("auto-accept responds for an unfocused server session", async ({ page }) => directory: directoryA, payload: { id: "event-permission-background-a", - type: "permission.asked", + type: "permission.v2.asked", properties: { id: "permission-background-a", sessionID: sessionA.id, - permission: "bash", - patterns: ["git status"], + action: "bash", + resources: ["git status"], metadata: {}, - always: [], + save: [], }, }, }) @@ -100,10 +100,9 @@ test("auto-accept responds for an unfocused server session", async ({ page }) => .toEqual([ { origin: serverA, - directory: directoryA, sessionID: sessionA.id, permissionID: "permission-background-a", - body: { response: "once" }, + body: { reply: "once" }, }, ]) @@ -111,14 +110,14 @@ test("auto-accept responds for an unfocused server session", async ({ page }) => directory: directoryA, payload: { id: "event-permission-background-a-child", - type: "permission.asked", + type: "permission.v2.asked", properties: { id: "permission-background-a-child", sessionID: childSessionA.id, - permission: "bash", - patterns: ["git diff"], + action: "bash", + resources: ["git diff"], metadata: {}, - always: [], + save: [], }, }, }) @@ -128,17 +127,15 @@ test("auto-accept responds for an unfocused server session", async ({ page }) => .toEqual([ { origin: serverA, - directory: directoryA, sessionID: sessionA.id, permissionID: "permission-background-a", - body: { response: "once" }, + body: { reply: "once" }, }, { origin: serverA, - directory: directoryA, sessionID: childSessionA.id, permissionID: "permission-background-a-child", - body: { response: "once" }, + body: { reply: "once" }, }, ]) }) @@ -169,8 +166,8 @@ async function mockServers(page: Page, permissionRequests: string[], permissionR const remote = url.origin === serverB const directory = remote ? directoryB : directoryA const sessions = remote ? [sessionB] : [sessionA, childSessionA] - const requestDirectory = url.searchParams.get("directory") - const response = url.pathname.match(/^\/session\/([^/]+)\/permissions\/([^/]+)$/) + const requestDirectory = url.searchParams.get("location[directory]") + const response = url.pathname.match(/^\/api\/session\/([^/]+)\/permission\/([^/]+)\/reply$/) if (route.request().method() === "POST" && response) { permissionResponses.push({ origin: url.origin, @@ -179,16 +176,21 @@ async function mockServers(page: Page, permissionRequests: string[], permissionR permissionID: response[2]!, body: route.request().postDataJSON(), }) - return json(route, true) + return route.fulfill({ status: 204, headers: { "access-control-allow-origin": "*" } }) } if (requestDirectory && requestDirectory !== directory) return json(route, { name: "InvalidDirectory" }, 500) if (url.pathname === "/global/event" || url.pathname === "/event" || url.pathname === "/api/event") return sse(route) if (url.pathname === "/global/health") return json(route, { healthy: true }) - if (url.pathname === "/api/provider" || url.pathname === "/api/model" || url.pathname === "/api/agent") - return json(route, { data: [] }) - if (url.pathname === "/api/model/default") return json(route, { data: null }) - if (["/api/command", "/api/reference", "/api/permission/request", "/api/question/request"].includes(url.pathname)) + const catalog = currentCatalog({ provider: provider(remote ? "server-b" : "server-a") }) + if (url.pathname === "/api/provider") return json(route, { data: catalog.providers }) + if (url.pathname === "/api/model") return json(route, { data: catalog.models }) + if (url.pathname === "/api/model/default") return json(route, { data: catalog.default }) + if (url.pathname === "/api/permission/request") { + permissionRequests.push(url.toString()) + return json(route, { location: { directory }, data: [] }) + } + if (["/api/agent", "/api/command", "/api/reference", "/api/question/request"].includes(url.pathname)) return json(route, { location: { directory }, data: [] }) if (url.pathname === "/api/mcp") return json(route, { location: { directory }, data: [] }) if (url.pathname === "/api/mcp/resource") @@ -206,7 +208,8 @@ async function mockServers(page: Page, permissionRequests: string[], permissionR } if (url.pathname === "/api/project/current") return json(route, { id: remote ? sessionB.projectID : "project-server-a", directory }) - if (url.pathname === "/api/session") return json(route, { data: sessions.map(currentSession), cursor: {} }) + if (url.pathname === "/api/session") + return json(route, { data: sessions.map((session) => currentSession(session)), cursor: {} }) if (url.pathname === "/api/session/active") return json(route, { data: {} }) const currentSessionInfo = sessions.find((session) => url.pathname === `/api/session/${session.id}`) if (currentSessionInfo) return json(route, { data: currentSession(currentSessionInfo) }) diff --git a/packages/app/e2e/regression/remote-tab-busy.spec.ts b/packages/app/e2e/regression/remote-tab-busy.spec.ts index 2d9b1e234971..e3ce916c43aa 100644 --- a/packages/app/e2e/regression/remote-tab-busy.spec.ts +++ b/packages/app/e2e/regression/remote-tab-busy.spec.ts @@ -2,7 +2,7 @@ import { expect, test, type Page, type Route } from "@playwright/test" import { base64Encode } from "@opencode-ai/core/util/encode" import { currentSession } from "../utils/mock-server" -const serverA = "http://127.0.0.1:4096" +const serverA = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` const serverB = "http://127.0.0.1:4097" const sessionA = session("ses_server_a", "C:/server-a", "Server A session") const sessionB = session("ses_server_b", "/home/server-b", "Server B session") diff --git a/packages/app/e2e/regression/review-line-comment.spec.ts b/packages/app/e2e/regression/review-line-comment.spec.ts index 566105e7646e..d72c69f483f4 100644 --- a/packages/app/e2e/regression/review-line-comment.spec.ts +++ b/packages/app/e2e/regression/review-line-comment.spec.ts @@ -146,10 +146,10 @@ async function openReview(page: Page) { const changes = page.getByRole("tab", { name: "Changes" }) const diffResponse = page.waitForResponse( (response) => - response.request().method() === "GET" && response.ok() && new URL(response.url()).pathname === "/api/vcs/diff", + response.request().method() === "GET" && response.ok() && new URL(response.url()).pathname === "/vcs/diff", ) await changes.click() - expect((await (await diffResponse).json()).data).toHaveLength(1) + expect(await (await diffResponse).json()).toHaveLength(1) await expect(page.getByRole("tab", { selected: true })).toHaveAccessibleName(/Files Changed/) const review = page.locator('[data-component="session-review"]') diff --git a/packages/app/e2e/regression/session-rename.spec.ts b/packages/app/e2e/regression/session-rename.spec.ts index 2fd366f6be11..34fc88723ac3 100644 --- a/packages/app/e2e/regression/session-rename.spec.ts +++ b/packages/app/e2e/regression/session-rename.spec.ts @@ -5,29 +5,12 @@ import { mockOpenCodeServer } from "../utils/mock-server" test.beforeEach(async ({ page }) => { const sessions = fixture.sessions.map((session) => ({ ...session })) await mockOpenCodeServer(page, { - protocol: "v1", sessions, provider: fixture.provider, directory: fixture.directory, project: fixture.project, currentPageMessages, }) - await page.route(/\/session\/[^/]+(?:\?.*)?$/, async (route) => { - if (route.request().method() !== "PATCH") return route.fallback() - const id = new URL(route.request().url()).pathname.split("/").at(-1) - const session = sessions.find((item) => item.id === id) - const payload: unknown = route.request().postDataJSON() - if ( - !session || - !payload || - typeof payload !== "object" || - !("title" in payload) || - typeof payload.title !== "string" - ) - throw new Error("Invalid rename request") - session.title = payload.title - await route.fulfill({ json: session, headers: { "access-control-allow-origin": "*" } }) - }) await page.addInitScript((directory) => { localStorage.setItem( "opencode.global.dat:server", @@ -69,8 +52,8 @@ test("cancels the session heading with Escape", async ({ page }) => { }) test("keeps the draft when saving the session heading fails", async ({ page }) => { - await page.route(/\/session\/[^/]+(?:\?.*)?$/, (route) => { - if (route.request().method() !== "PATCH") return route.fallback() + await page.route(/\/api\/session\/[^/]+\/rename(?:\?.*)?$/, (route) => { + if (route.request().method() !== "POST") return route.fallback() return route.fulfill({ status: 500, headers: { "access-control-allow-origin": "*" } }) }) await page.getByRole("heading", { name: fixture.expected.targetTitle, exact: true }).click() diff --git a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts index b969b590d89b..85f00a76130b 100644 --- a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts +++ b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts @@ -2,7 +2,7 @@ import { expect, test, type Page, type Route } from "@playwright/test" import { base64Encode } from "@opencode-ai/core/util/encode" import { currentSession } from "../utils/mock-server" -const server = "http://127.0.0.1:4096" +const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` const sessionA = session("ses_tab_a", "Tab A session") const sessionB = session("ses_tab_b", "Tab B session") const sessionC = session("ses_tab_c", "Tab C session") @@ -92,7 +92,8 @@ async function mockServer(page: Page) { if (url.pathname === "/global/event" || url.pathname === "/event" || url.pathname === "/api/event") return sse(route) if (url.pathname === "/global/health") return json(route, { healthy: true }) - if (url.pathname === "/api/session") return json(route, { data: sessions.map(currentSession), cursor: {} }) + if (url.pathname === "/api/session") + return json(route, { data: sessions.map((session) => currentSession(session)), cursor: {} }) if (url.pathname === "/api/session/active") return json(route, { data: {} }) const currentSessionInfo = sessions.find((item) => url.pathname === `/api/session/${item.id}`) if (currentSessionInfo) return json(route, { data: currentSession(currentSessionInfo) }) diff --git a/packages/app/e2e/tsconfig.json b/packages/app/e2e/tsconfig.json index 08cdf76b7f26..0f66f54c3a50 100644 --- a/packages/app/e2e/tsconfig.json +++ b/packages/app/e2e/tsconfig.json @@ -25,6 +25,11 @@ "./regression/review-state-persistence.spec.ts", "./regression/session-rename.spec.ts", "./regression/project-picker-recent-search.spec.ts", - "./regression/session-model-timeline-scroll.spec.ts" + "./regression/session-model-timeline-scroll.spec.ts", + "./regression/cross-server-tab-close.spec.ts", + "./regression/remote-session-settings.spec.ts", + "./regression/remote-tab-busy.spec.ts", + "./regression/review-line-comment.spec.ts", + "./regression/tab-navigate-mousedown.spec.ts" ] } diff --git a/packages/app/e2e/utils/mock-server.ts b/packages/app/e2e/utils/mock-server.ts index 9d06d8e8dcb3..fdb4df408880 100644 --- a/packages/app/e2e/utils/mock-server.ts +++ b/packages/app/e2e/utils/mock-server.ts @@ -242,6 +242,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { if (/^\/api\/pty\/[^/]+\/connect-token$/.test(path)) return json(route, { location: location(config), data: { ticket: "e2e-ticket", expires_in: 60 } }) if (emptyObject.has(path)) return json(route, {}) + if (path === "/vcs/diff") return json(route, config.vcsDiff ?? []) if (emptyList.has(path)) return json(route, []) if (path === "/api/session") { const directory = url.searchParams.get("directory") @@ -335,6 +336,13 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { if (/^\/session\/[^/]+\/permissions\/[^/]+$/.test(path) && route.request().method() === "POST") { return json(route, true) } + const renameMatch = path.match(/^\/api\/session\/([^/]+)\/rename$/) + if (renameMatch && route.request().method() === "POST") { + const session = config.sessions.find((item) => item.id === renameMatch[1]) + if (!session) return json(route, { error: "Session not found" }, undefined, 404) + session.title = route.request().postDataJSON().title + return route.fulfill({ status: 204, headers: { "access-control-allow-origin": "*" } }) + } if ( /^\/api\/session\/[^/]+\/(archive|rename|interrupt|revert\/clear|revert\/commit)$/.test(path) && route.request().method() === "POST" @@ -484,7 +492,7 @@ function location(config: MockServerConfig) { } } -function currentCatalog(config: MockServerConfig) { +export function currentCatalog(config: Pick) { const value = typeof config.provider === "function" ? config.provider() : config.provider if (!value || typeof value !== "object") return { providers: [], models: [], default: null } const catalog = value as { diff --git a/packages/app/src/components/titlebar-tab-gesture.test.ts b/packages/app/src/components/titlebar-tab-gesture.test.ts index a6ab89924872..d1dffcc43480 100644 --- a/packages/app/src/components/titlebar-tab-gesture.test.ts +++ b/packages/app/src/components/titlebar-tab-gesture.test.ts @@ -1,5 +1,11 @@ import { describe, expect, test } from "bun:test" -import { canOpenTabRename, canStartTabDrag, forwardTabRef, isTabCloseTarget } from "./titlebar-tab-gesture" +import { + canOpenTabRename, + canStartTabDrag, + forwardTabRef, + isTabCloseTarget, + openTabContextMenu, +} from "./titlebar-tab-gesture" describe("titlebar tab gestures", () => { test("excludes close controls from tab gestures", () => { @@ -31,3 +37,38 @@ describe("titlebar tab gestures", () => { expect(canStartTabDrag("touch")).toBe(false) }) }) + +describe("tab keyboard context menu", () => { + for (const key of ["ContextMenu", "F10"]) { + test(`opens from ${key} without relying on browser native synthesis`, () => { + const tab = document.createElement("a") + const menus: Event[] = [] + tab.addEventListener("contextmenu", (event) => menus.push(event)) + tab.addEventListener("keydown", (event) => + openTabContextMenu(event as KeyboardEvent & { currentTarget: HTMLElement }), + ) + const event = new KeyboardEvent("keydown", { key, shiftKey: key === "F10", cancelable: true }) + tab.dispatchEvent(event) + expect(menus).toHaveLength(1) + expect(event.defaultPrevented).toBe(true) + }) + } + + test("leaves unrelated or already-handled keys alone", () => { + const tab = document.createElement("a") + const menus: Event[] = [] + tab.addEventListener("contextmenu", (event) => menus.push(event)) + tab.addEventListener("keydown", (event) => + openTabContextMenu(event as KeyboardEvent & { currentTarget: HTMLElement }), + ) + for (const init of [{ key: "F10" }, { key: "Enter" }, { key: "F10", shiftKey: true, ctrlKey: true }]) { + const event = new KeyboardEvent("keydown", { ...init, cancelable: true }) + tab.dispatchEvent(event) + expect(event.defaultPrevented).toBe(false) + } + const event = new KeyboardEvent("keydown", { key: "ContextMenu", cancelable: true }) + event.preventDefault() + tab.dispatchEvent(event) + expect(menus).toHaveLength(0) + }) +}) diff --git a/packages/app/src/components/titlebar-tab-gesture.ts b/packages/app/src/components/titlebar-tab-gesture.ts index 0aa51189363d..6b11188cf341 100644 --- a/packages/app/src/components/titlebar-tab-gesture.ts +++ b/packages/app/src/components/titlebar-tab-gesture.ts @@ -15,3 +15,14 @@ export function forwardTabRef(ref: Ref | undefined, element: HTM export function canOpenTabRename(dragging: boolean | undefined, editing: boolean, pending: boolean) { return !dragging && !editing && !pending } + +export function openTabContextMenu(event: KeyboardEvent & { currentTarget: HTMLElement }) { + if (event.defaultPrevented || event.ctrlKey || event.altKey || event.metaKey) return + if (event.key !== "ContextMenu" && !(event.key === "F10" && event.shiftKey)) return + event.preventDefault() + const rect = event.currentTarget.getBoundingClientRect() + // Firefox does not reliably synthesize contextmenu for keyboard activation. + event.currentTarget.dispatchEvent( + new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: rect.left, clientY: rect.bottom }), + ) +} diff --git a/packages/app/src/components/titlebar-tab-nav.tsx b/packages/app/src/components/titlebar-tab-nav.tsx index b016f286a250..e162c43ed7b6 100644 --- a/packages/app/src/components/titlebar-tab-nav.tsx +++ b/packages/app/src/components/titlebar-tab-nav.tsx @@ -12,7 +12,7 @@ import { ServerConnection, serverName } from "@/context/server" import { displayName, projectForSession } from "@/pages/layout/helpers" import { SessionTabAvatar } from "@/pages/layout/session-tab-avatar" import type { Session } from "@opencode-ai/sdk/v2" -import { canOpenTabRename, forwardTabRef } from "./titlebar-tab-gesture" +import { canOpenTabRename, forwardTabRef, openTabContextMenu } from "./titlebar-tab-gesture" import { TabPreviewPopover } from "./titlebar-tab-popover" import "./titlebar-tab-nav.css" @@ -209,6 +209,10 @@ export function TabNavItem(props: { data-titlebar-tab-link href={props.href} draggable={false} + onKeyDown={(event) => { + if (editing() || props.dragging) return + openTabContextMenu(event) + }} onDragStart={(event) => { event.preventDefault() event.stopPropagation() From f4d3fbd7b2b6a2b3198008b563a77c62ecc4ab14 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 00:19:45 -0300 Subject: [PATCH 019/129] fix(app): align context meter with staged rollback selection --- .../regression/session-context-usage.spec.ts | 83 +++++++++++++++++++ packages/app/e2e/tsconfig.json | 1 + .../src/components/session-context-usage.tsx | 7 +- 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 packages/app/e2e/regression/session-context-usage.spec.ts diff --git a/packages/app/e2e/regression/session-context-usage.spec.ts b/packages/app/e2e/regression/session-context-usage.spec.ts new file mode 100644 index 000000000000..722d04ddbb82 --- /dev/null +++ b/packages/app/e2e/regression/session-context-usage.spec.ts @@ -0,0 +1,83 @@ +import { SessionMessage } from "@opencode-ai/schema/session-message" +import { Schema } from "effect" +import { expect, test } from "@playwright/test" +import { base64Encode } from "@opencode-ai/core/util/encode" +import { mockOpenCodeServer } from "../utils/mock-server" +import { expectSessionTitle } from "../utils/waits" + +const directory = "C:/OpenCode/ContextUsageRegression" +const sessionID = "ses_context_usage" +const model = { providerID: "opencode", modelID: "test" } +const messages = [0, 1].flatMap((index) => [ + { + id: `msg_${index * 2 + 1}_user`, + type: "user" as const, + text: `Prompt ${index}`, + files: [], + agents: [], + time: { created: index * 2 + 1 }, + payload: { version: 1, agent: "build", model, parts: [{ type: "text", text: `Prompt ${index}` }] }, + }, + { + id: `msg_${index * 2 + 2}_assistant`, + type: "assistant" as const, + agent: "build", + model: { providerID: model.providerID, id: model.modelID }, + time: { created: index * 2 + 2, completed: index * 2 + 3 }, + cost: 1.25, + tokens: { input: 20_000 * (index + 1), output: 5_000 * (index + 1), reasoning: 0, cache: { read: 0, write: 0 } }, + content: [{ id: `txt_${index}`, type: "text" as const, text: `Answer ${index}` }], + }, +]) +Schema.decodeUnknownSync(Schema.Array(SessionMessage.Message))(messages, { onExcessProperty: "error" }) + +for (const reverted of [false, true]) { + test(`context circle and detail agree ${reverted ? "after rollback" : "with recorded usage"}`, async ({ page }) => { + await mockOpenCodeServer(page, { + directory, + project: { id: "proj_context_usage", worktree: directory, time: { created: 1, updated: 1 }, sandboxes: [] }, + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { test: { id: "test", name: "Test", limit: { context: 100_000 } } }, + }, + ], + connected: ["opencode"], + default: model, + }, + sessions: [ + { + id: sessionID, + directory, + title: "Context usage regression", + cost: 2.5, + time: { created: 1, updated: 5 }, + ...(reverted ? { revert: { messageID: "msg_3_user", inclusive: true } } : {}), + }, + ], + currentPageMessages: () => ({ items: messages.toReversed(), throughSeq: 0 }), + }) + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await expectSessionTitle(page, "Context usage regression") + const usage = page.getByRole("button", { name: "View context usage", exact: true }).first() + const percentage = reverted ? 25 : 50 + await expect + .poll(async () => + usage.locator('circle[data-slot$="-progress"]').evaluate((circle) => { + const total = Number(circle.getAttribute("stroke-dasharray")) + return Math.round(100 * (1 - Number(circle.getAttribute("stroke-dashoffset")) / total)) + }), + ) + .toBe(percentage) + await usage.hover() + await expect(page.getByRole("tooltip")).toContainText(`${percentage}%`) + await expect(page.getByRole("tooltip")).toContainText((percentage * 1_000).toLocaleString("en-US")) + await usage.click() + await expect(page.getByText("Total Tokens", { exact: true }).locator("..")).toContainText( + (percentage * 1_000).toLocaleString("en-US"), + ) + await expect(page.getByText("Usage", { exact: true }).last().locator("..")).toContainText(`${percentage}%`) + }) +} diff --git a/packages/app/e2e/tsconfig.json b/packages/app/e2e/tsconfig.json index 0f66f54c3a50..6c25dde7e933 100644 --- a/packages/app/e2e/tsconfig.json +++ b/packages/app/e2e/tsconfig.json @@ -19,6 +19,7 @@ "./regression/session-timeline-context-resize.spec.ts", "./utils/**/*.ts", "./regression/session-pending-reload.spec.ts", + "./regression/session-context-usage.spec.ts", "./regression/session-rollback-queue.spec.ts", "./regression/session-model-selection.spec.ts", "./regression/session-timeline-reasoning-projection.spec.ts", diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index b2b446234372..4f970032b862 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -12,6 +12,7 @@ import { useLanguage } from "@/context/language" import { useProviders } from "@/hooks/use-providers" import { useSDK } from "@/context/sdk" import { getSessionContext } from "@/components/session/session-context-metrics" +import { selectSessionContextMessages } from "@/components/session/session-context-system" import { useSessionLayout } from "@/pages/session/session-layout" import { createSessionTabs } from "@/pages/session/helpers" import { useSettings } from "@/context/settings" @@ -72,7 +73,11 @@ export function SessionContextUsage(props: SessionContextUsageProps) { }), ) - const context = createMemo(() => getSessionContext(props.messages(), [...providers.all().values()])) + const context = createMemo(() => + getSessionContext(selectSessionContextMessages(props.messages(), props.session()?.revert), [ + ...providers.all().values(), + ]), + ) const cost = createMemo(() => { return usd().format(props.session()?.cost ?? 0) }) From 87b8ce56c315e9615c45342afb7603a8480807b0 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 00:20:32 -0300 Subject: [PATCH 020/129] docs(app): record upstream comparison and remaining release gates --- docs/fork/web-audit.md | 266 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 docs/fork/web-audit.md diff --git a/docs/fork/web-audit.md b/docs/fork/web-audit.md new file mode 100644 index 000000000000..980a67c5ad84 --- /dev/null +++ b/docs/fork/web-audit.md @@ -0,0 +1,266 @@ +# Fork web compatibility audit + +Updated: 2026-09-05. + +## Release decision + +**Not ready to merge.** The integration branches contain upstream through +`70b4ca8c181e4c1ac6d8993b86249d824487ec65` and verified fixes, but the full browser +suite is not green. Keep production PR #7 and development PR #8 as drafts. +No finite audit establishes that the entire fork is bug-free. + +The deployed service on ports 4096/14096 and its binary were not changed. Tests +used separate worktrees, loopback servers, synthetic sessions and isolated XDG +data. Real-server smoke checks were unauthenticated; they do not establish +Basic-auth deployment parity. The replacement transport has a separate unit +test asserting Basic-auth headers with dummy credentials. + +## Comparison boundary + +| Ref | Commit | +| ----------------------------------------------------- | ------------------------------------------ | +| Fork production baseline | `c94eb6133eca258cb06cc30d159aae7a76519d1b` | +| Fork development baseline | `722450048f` | +| Initial upstream production | `20a7743876` | +| Initial upstream development | `79903a4cf7` | +| Latest upstream fetched and integrated, both branches | `70b4ca8c181e4c1ac6d8993b86249d824487ec65` | + +The initial production delta was 510 files, 38,512 additions / 7,297 deletions. +The latest upstream merge did not change app/session-ui runtime source relative +to the initial upstream production snapshot. It did bring release, provider and +console changes, so both integration branches received full unit/type validation. + +Fork-heavy boundaries remain the current-session model/reducer, timeline +presentation, prompt submit path, session queue, model persistence and shared +message-part rendering. These are intentional features, not candidates for +wholesale upstream replacement. App code still composes a pinned promise client, +a separate pinned current-session client, generated legacy SDK, and compatibility +stores. Regenerating the workspace client alone does not update the pinned clients. + +### Functional comparison + +Pristine latest upstream passed all 10 selected Chromium tests: reasoning-selector +visibility, eight reasoning/timeline profiles, and review-state persistence. +The fork passes those corresponding contracts with its native-session fixtures, +plus its additional queue/replacement/pending/model-selection regressions. + +An attempted identical-fixture comparison was invalid: fork-only payload fields +failed upstream schema validation and the upstream UI did not subscribe to the +fork's native session-event endpoint. Those failures are **not** counted as +upstream product bugs. Compare observable contracts using each protocol's valid +fixtures; do not silently rewrite either runtime to fit the other's mocks. + +## Reproduced defects and fixes + +### 1. Admitted steering disappeared after reload + +Admission is durable but transcript promotion occurs later at a runner boundary. +The web snapshot lacked pending admissions; replay starts at the snapshot's +watermark, so already-admitted input could not be reconstructed from later events. + +The message endpoint now includes an optional pending-user projection, selected +from unpromoted, undiscarded steering rows. Queue inputs remain separate. The +current-session model hydrates that projection and ignores stale pending snapshots +that would resurrect discarded input. Neither this query nor stop promotes or +resumes input. New mapping logic is isolated in `pending-inputs.ts`. + +Evidence: database selection/interrupt tests, reducer stale-snapshot test, model +hydration test, cross-browser reload test, and an isolated real-server interrupt +followed by browser reload. Admitted text remained visible and Stop was absent +when execution was idle. + +### 2. Rollback editing disagreed with upstream revert semantics + +Upstream revert retains its boundary message. The fork web editor hides that +message and means to replace it. Pending admissions also are not projected +message boundaries. Passing an extra flag to the pinned promise client was not +sufficient: its encoder omitted the unknown field. + +Inclusive replacement is now explicit and opt-in. Default upstream behavior is +unchanged. Staging captures an admission cutoff without deleting input. Commit +removes the selected original and subsequent pre-cutoff steering, preserves +explicit pending queues, and preserves the replacement admitted after staging. +The runner still commits before promoting new input. The focused persistence +logic lives in `revert-replacement.ts`; `server-revert.ts` bridges only the opt-in +request through the generated SDK. Clients were regenerated by repository scripts. + +The current-session projection is also updated optimistically and refreshed after +commit. Rollback no longer continues after an interruption error; it restores the +draft and shows a request-failed notification. Both composer implementations send +the explicit replacement flag on Enter. + +Evidence: 106 runner/projector/replacement tests passed, including default upstream +boundary retention. The live API check staged a pending original, admitted an +edited replacement, committed, and returned only the edited pending message. +Cross-browser tests cover Enter replacement, retained queue, and interruption +failure. Multi-client concurrent stage/resume remains a coverage gap. + +### 3. Review metadata and diff routes drifted across clients + +Current-session protocol detection did not imply support for `/api/vcs`. The +isolated server returned UI HTML for that unsupported path, and bootstrap skipped +legacy branch metadata on V2. The fix reuses the existing VCS compatibility adapter +and loads branch metadata from the supported endpoint. No new parallel VCS +implementation was introduced. + +Evidence: transport tests assert `/vcs/diff`, working-to-git mode translation, +directory placement and returned content; bootstrap tests assert branch metadata; +review persistence passes in both browsers. Review-line fixtures were corrected +to observe the actual compatibility endpoint rather than a nonexistent V2 route. + +### 4. Switching models reset timeline scroll + +The recent-model resource used a suspending read. A selection changed recent +models and briefly detached the session subtree, resetting the same scroll node +to zero. Browser instrumentation confirmed removal/reinsertion under `main`, +rather than a new session or new scroll node. The resource code is shared with +upstream; this is not evidence that every upstream UI configuration exhibits it. + +A one-line read of the resource's latest value prevents that suspension without +restructuring the layout. All six Chromium/Firefox model-switch scroll regressions +pass, including variant/no-variant changes and unchanged composer dimensions. + +### 5. Test and CI drift obscured regressions + +Mocks dropped model variants, assigned obsolete release dates, ignored configured +agents, lacked context/pending snapshots, and left rename responses non-mutating. +Several tests imported a removed pagination helper or asserted legacy part IDs +against normalized native rendering. Other fixtures hardcoded the local server +port, defeating isolated-port runs. Corrected fixtures retain semantic assertions; +no failing tests were skipped or weakened to force a green result. + +Three HTTP API exerciser assertions expected raw project values while the declared +Effect API uses located envelopes. Assertions now validate location/project and +the nested data explicitly. Coverage/auth/effect runs each passed 236 scenarios, +with no missing or skipped route scenarios. + +Fork CI previously selected upstream-only Blacksmith runners. It now selects +GitHub-hosted runners on the fork, checks production as well as dev, and typechecks +the addressed browser fixtures. Scheduled upstream sync now runs validation before +pushing because its GITHUB_TOKEN push does not trigger normal push workflows. +Actionlint syntax validation passed; optional shellcheck still reports existing +SC2129 style findings in the sync script, not new syntax errors. + +### 6. Firefox could not open the tab context menu with Shift+F10 + +A focused tab received both keydown events but no native `contextmenu` event. +The Kobalte trigger handles `contextmenu` and pointer events, not these keyboard +keys. The tab now explicitly routes Shift+F10 and ContextMenu to that existing +trigger, anchored below the focused tab. The helper is isolated in the existing +fork tab-gesture module; editing/dragging guards stay at the integration point. +Preventing the default avoids duplicate native synthesis on other browsers. + +Evidence: repeated Firefox failure before the fix, keyboard-event instrumentation, +three new unit cases, and all 16 rename tests passing in Chromium/Firefox. The +keyboard assertion was not replaced with a pointer click or skipped. + +### 7. Context circle counted messages hidden by staged rollback + +The message prop fix resolved the always-zero circle, but a second discrepancy +remained: the detail screen selects context through the staged revert boundary; +the circle used the full context snapshot. Staging does not delete that snapshot. +The circle now reuses `selectSessionContextMessages`, the same existing helper as +the detail screen. This is one import and one targeted call-site integration. + +Evidence: a schema-validated browser fixture records 50,000 of 100,000 tokens before +rollback and 25,000 after rollback. The rollback circle failed at 50% before this +fix. All four Chromium/Firefox cases now assert the SVG's numeric progress, +tooltip token count, and detail-screen total/percentage against explicit values. + +## Validation record + +- Both latest-upstream integration branches: **30/30 typecheck tasks and 10/10 + full unit-test tasks passed**. Each core suite: 1,142 tests. Each opencode suite: + 3,650 tests. Latest app suite: 793 tests. Generated-client check passed. +- Focused fork browser run: **32/32 passed** across Chromium and Firefox; separate + model-switch scroll run: **6/6 passed**. +- Initial full browser run: **135 passed, 96 failed, 3 pre-existing skips**. + Latest full rerun: **178 passed, 55 failed, 3 pre-existing skips** (236 cases, + 6.3 minutes). This rerun includes keyboard/remote-fixture fixes and precedes the + final context-selection fix; the latter separately passed all four new cases. + Neither run is an all-green result. +- Rename tests: **16/16 passed** across browsers after correcting native rename + fixtures and keyboard menu activation. Remote settings/auto-accept tests: + **4/4 passed**, including unfocused parent/child sessions on a different server. + The actual current request query is `location[directory]`, not `directory`; + current replies are session-addressed and use `{ reply: "once" }`. +- CI is not green. Windows development jobs failed before tests during Bun 1.3.14 + patch installation (`ENOTEMPTY` for patched `@ai-sdk/openai-compatible`), even + with no restored cache. Linux development unit CI hit subprocess timeouts in + `run-process.test.ts`; local full runs passed. Production typechecking was + cancelled, not a demonstrated compiler error. These require investigation and + successful reruns; no timeouts or assertions have been weakened. +- Manual browser against isolated source backend: edited pending message visible, + original absent, no Stop while idle; Muse Spark 1.3 Free offered Default, + Minimal, Low, Medium, High and Xhigh reasoning choices. No paid inference used. +- Production tab-switch benchmark: both before/after runs passed. V2 median stable + times, milliseconds: + + | Scenario | Before | After | + | ------------------- | -----: | ----: | + | Review closed, cold | 136.2 | 142.8 | + | Review closed, hot | 103.7 | 115.1 | + | Review open, cold | 120.6 | 120.4 | + | Review open, hot | 112.6 | 113.4 | + + Five local samples per scenario, not a statistical performance guarantee. The + final after run includes latest integrated upstream, the recent-model fix, and + the context-selection fix. These samples include concurrent local browser work; + the increases are not established as code-induced regressions or dismissed as + harmless. Repeat isolated performance measurements before release. + No wrong-destination or review-host replacement samples were observed. + +## Remaining full-suite failure inventory + +These are failing test cases, not 55 confirmed product bugs. Several fixtures still +send mutation arrays as one SSE event or assert obsolete part IDs. Repair invalid +fixtures without weakening observable behavior assertions; investigate failures +that remain against valid data. + +| Area | Cases | +| ---------------------------------- | ----: | +| Native timeline transport | 14 | +| Timeline projection | 6 | +| Smoke pagination/timeline | 6 | +| Collapse state | 4 | +| History-root transitions | 4 | +| Subagent navigation | 4 | +| Context resize | 3 | +| Lifecycle/retry | 3 | +| Request docks | 2 | +| Reducer projection | 2 | +| Shell outline | 2 | +| Todo navigation | 2 | +| New-project model-selection story | 2 | +| Review/terminal stacking (Firefox) | 1 | + +## Ordered remaining work + +1. [x] Correct remote settings fixtures and verify cross-server auto-accept, + including unfocused parent/child sessions. This was fixture protocol drift, + not evidence of a runtime permissions defect. +2. [x] Resolve Firefox keyboard context-menu opening while retaining the keyboard + assertion. Rename, tab close and focus restoration pass on both browsers. +3. Migrate remaining timeline transport fixtures to individual native events and + native normalized part identities. Audit actual render behavior for collapse, + retry, context resize, history root, comments, attachments and subagent cards. +4. Finish request-dock, todo, smoke pagination and new-project/model user-story + validation. All configured test ports must be honored. Extend e2e typechecking + to the remaining regression files; current coverage is intentionally enumerated, + not a claim that every e2e file typechecks. +5. Audit keyboard undo/redo interruption-error handling and compatibility-store + reads. Main rollback is fixed; other entrypoints must receive equivalent tests. + Add multi-client replacement/cutoff tests. Basic and staged-rollback context + usage browser parity is now covered; live usage updates remain to be audited. +6. Run the complete browser suite and final benchmark on both integration heads; + inspect Linux and Windows CI. Update the PR validation record with final results. +7. Re-fetch upstream, validate any additional commits, then merge both PRs and + verify upstream ancestry on dev and production. + Do not deploy or reinstall the user's running service as an incidental step. + +## Mergeability rule + +Keep fork behavior behind focused modules and optional protocol fields. Prefer +small call-site adapters over editing vendored client archives or broadly rewriting +upstream UI. Preserve upstream defaults. Every future sync must validate these +boundaries, not merely resolve textual merge conflicts. From 0526aba384bba46f81a0040eebdf9fc9448ff494 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 09:10:22 -0300 Subject: [PATCH 021/129] fix(app): preserve current session state across compatibility boundaries Preserve native prompt payloads and task metadata, refresh after early interruption, bridge retry state, and share rollback mutations with keyboard commands. Extend native browser fixtures and replacement consistency tests. This is a draft audit checkpoint: unresolved browser and performance failures are recorded in the audit report before merge. --- .../timeline-stability/fixture.test.ts | 3 +- .../performance/timeline-stability/fixture.ts | 6 +- .../review-terminal-stacked.spec.ts | 49 +++++++--- .../regression/session-context-usage.spec.ts | 56 +++++++++-- .../regression/session-pending-reload.spec.ts | 34 ++++++- .../regression/session-request-docks.spec.ts | 39 ++++---- .../regression/session-rollback-queue.spec.ts | 45 +++++++++ .../session-timeline-collapse-state.spec.ts | 6 +- .../session-timeline-context-resize.spec.ts | 22 +---- .../session-timeline-history-root.spec.ts | 26 +++--- .../session-timeline-lifecycle-state.spec.ts | 11 ++- .../session-timeline-projection.spec.ts | 4 +- ...ession-timeline-reducer-projection.spec.ts | 3 +- .../session-timeline-shell-outline.spec.ts | 10 +- .../session-timeline-transport.spec.ts | 74 ++++++++------- .../session-todo-dock-navigation.spec.ts | 71 +++++++++----- .../subagent-child-navigation.spec.ts | 4 +- .../app/e2e/smoke/session-timeline.fixture.ts | 17 +++- .../app/e2e/smoke/session-timeline.spec.ts | 93 +++++++++++++------ packages/app/e2e/tsconfig.json | 15 ++- .../user-story/model-selection-flow.spec.ts | 22 +++-- packages/app/e2e/utils/mock-server.ts | 13 ++- .../app/src/components/prompt-input-v2.tsx | 1 + packages/app/src/components/prompt-input.tsx | 1 + .../src/components/prompt-input/contracts.ts | 1 + .../components/prompt-input/submit.test.ts | 5 +- .../app/src/components/prompt-input/submit.ts | 2 + .../src/context/global-sync/bootstrap.test.ts | 7 ++ .../app/src/context/global-sync/bootstrap.ts | 7 +- .../app/src/context/server-session.test.ts | 27 ++++++ packages/app/src/context/server-session.ts | 4 - .../app/src/hooks/provider-catalog.test.ts | 37 +++++++- packages/app/src/hooks/provider-catalog.ts | 25 +++++ packages/app/src/hooks/use-providers.ts | 26 +++++- packages/app/src/pages/session.tsx | 31 +++++-- .../src/pages/session/current/model.test.ts | 63 +++++++++++++ .../session/timeline/message-timeline.tsx | 2 +- .../pages/session/use-session-commands.tsx | 24 +++-- .../app/src/utils/session-message.test.ts | 82 ++++++++++++++++ packages/app/src/utils/session-message.ts | 43 +++++++-- .../core/test/session-replacement.test.ts | 32 ++++++- 41 files changed, 816 insertions(+), 227 deletions(-) diff --git a/packages/app/e2e/performance/timeline-stability/fixture.test.ts b/packages/app/e2e/performance/timeline-stability/fixture.test.ts index 67a1cfb21466..20ad00441aee 100644 --- a/packages/app/e2e/performance/timeline-stability/fixture.test.ts +++ b/packages/app/e2e/performance/timeline-stability/fixture.test.ts @@ -51,10 +51,11 @@ describe("timeline fixture validation", () => { }) const second = event("session.next.retried", { timestamp: 2, - sessionID: "ses_timeline_stability", + sessionID: "ses_other_timeline", attempt: 2, error: { message: "retry", isRetryable: true }, }) + expect(second.durable?.aggregateID).toBe("ses_other_timeline") expect(first.id).toMatch(/^evt_timeline_\d{4}$/) expect(Number(second.id.slice(-4))).toBe(Number(first.id.slice(-4)) + 1) }) diff --git a/packages/app/e2e/performance/timeline-stability/fixture.ts b/packages/app/e2e/performance/timeline-stability/fixture.ts index 3b6041770ef0..5cd8a459a2e6 100644 --- a/packages/app/e2e/performance/timeline-stability/fixture.ts +++ b/packages/app/e2e/performance/timeline-stability/fixture.ts @@ -137,7 +137,9 @@ export async function setupTimeline( } await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) await transport.waitForConnection() - await expectSessionTitle(page, title) + if (input.settings?.newLayoutDesigns === false) + await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible() + else await expectSessionTitle(page, title) await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => resolve()))) if (input.cpuRate && input.cpuRate > 1) { const devtools = await page.context().newCDPSession(page) @@ -219,7 +221,7 @@ export function event(type: EventType, data: TimelineEvent["data"]): TimelineEve ...(durable ? { durable: { - aggregateID: sessionID, + aggregateID: data.sessionID, seq: ++durableSequence, version: type === "session.next.step.ended" || type === "session.next.step.failed" ? 2 : 1, }, diff --git a/packages/app/e2e/regression/review-terminal-stacked.spec.ts b/packages/app/e2e/regression/review-terminal-stacked.spec.ts index 79b564820e20..fdd66320b741 100644 --- a/packages/app/e2e/regression/review-terminal-stacked.spec.ts +++ b/packages/app/e2e/regression/review-terminal-stacked.spec.ts @@ -1,5 +1,6 @@ import { expect, test, type Page } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" +import { installSseTransport } from "../utils/sse-transport" import { expectSessionTitle } from "../utils/waits" const directory = "C:/OpenCode/ReviewTerminalStacked" @@ -19,13 +20,16 @@ const branchDiffs = [ test("keeps the review tree and terminal sized when both panels are open", async ({ page }) => { test.setTimeout(120_000) - const events: Array<{ directory: string; payload: Record }> = [] + const transport = await installSseTransport(page, { + server: `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`, + path: `/api/session/${sessionID}/event`, + }) const sessionStatus = { [sessionID]: { type: "idle" as "busy" | "idle" } } let detailVersion = 1 let detailFailures = 1 await page.setViewportSize({ width: 1400, height: 900 }) await mockOpenCodeServer(page, { - protocol: "v1", + protocol: "v2", directory, project: { id: projectID, @@ -58,9 +62,19 @@ test("keeps the review tree and terminal sized when both panels are open", async }, ], sessionStatus: () => sessionStatus, - pageMessages: () => ({ items: [] }), - events: () => events.splice(0, 1), - eventRetry: 16, + currentPageMessages: () => ({ + items: [ + { + id: "msg_review_user", + type: "user", + text: "Review changes", + files: [], + agents: [], + time: { created: 1700000000000 }, + }, + ], + throughSeq: 0, + }), }) await page.route(/\/vcs(?:\?.*)?$/, (route) => route.fulfill({ @@ -148,6 +162,7 @@ test("keeps the review tree and terminal sized when both panels are open", async await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) await expectSessionTitle(page, title) + await transport.waitForConnection() await expect(page.locator("#review-panel")).toBeVisible() await expectTree(page, 8, "git-0.ts") @@ -159,8 +174,7 @@ test("keeps the review tree and terminal sized when both panels are open", async await expectStackGeometry(page) const treeViewport = page.locator('#review-panel [data-slot="session-review-v2-sidebar-tree"] .scroll-view__viewport') - await treeViewport.hover() - await page.mouse.wheel(0, 100_000) + await treeViewport.evaluate((element) => element.scrollTo({ top: element.scrollHeight, behavior: "instant" })) await expect .poll(() => treeViewport.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop)) .toBeLessThanOrEqual(1) @@ -185,7 +199,7 @@ test("keeps the review tree and terminal sized when both panels are open", async await expect(preview).toContainText("after-1") detailVersion = 2 sessionStatus[sessionID] = { type: "busy" } - events.push(statusEvent("busy")) + await transport.send(statusEvent("busy")) await expect(page.getByRole("button", { name: "Stop" })).toBeVisible() const refreshedDiff = page.waitForRequest((request) => { const url = new URL(request.url()) @@ -195,7 +209,7 @@ test("keeps the review tree and terminal sized when both panels are open", async ) }) sessionStatus[sessionID] = { type: "idle" } - events.push(statusEvent("idle")) + await transport.send(statusEvent("idle")) await refreshedDiff await expect(preview).toContainText("after-2") await selectMode(page, "Branch changes", "Git changes") @@ -288,8 +302,21 @@ function base64Encode(value: string) { function statusEvent(type: "busy" | "idle") { return { - directory, - payload: { type: "session.status", properties: { sessionID, status: { type } } }, + id: `evt_review_${type}`, + type: type === "busy" ? "session.next.step.started" : "session.next.step.ended", + durable: { aggregateID: sessionID, seq: type === "busy" ? 1 : 2, version: type === "busy" ? 1 : 2 }, + data: { + timestamp: 1700000001000, + sessionID, + assistantMessageID: "msg_review_assistant", + ...(type === "busy" + ? { agent: "build", model: { providerID: "opencode", id: "test" } } + : { + finish: "stop", + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + }), + }, } } diff --git a/packages/app/e2e/regression/session-context-usage.spec.ts b/packages/app/e2e/regression/session-context-usage.spec.ts index 722d04ddbb82..0461fc378597 100644 --- a/packages/app/e2e/regression/session-context-usage.spec.ts +++ b/packages/app/e2e/regression/session-context-usage.spec.ts @@ -4,6 +4,8 @@ import { expect, test } from "@playwright/test" import { base64Encode } from "@opencode-ai/core/util/encode" import { mockOpenCodeServer } from "../utils/mock-server" import { expectSessionTitle } from "../utils/waits" +import { installSseTransport } from "../utils/sse-transport" +import { event, type TimelineEvent } from "../performance/timeline-stability/fixture" const directory = "C:/OpenCode/ContextUsageRegression" const sessionID = "ses_context_usage" @@ -31,8 +33,14 @@ const messages = [0, 1].flatMap((index) = ]) Schema.decodeUnknownSync(Schema.Array(SessionMessage.Message))(messages, { onExcessProperty: "error" }) -for (const reverted of [false, true]) { - test(`context circle and detail agree ${reverted ? "after rollback" : "with recorded usage"}`, async ({ page }) => { +for (const mode of ["recorded", "rollback", "live"] as const) { + test(`context circle and detail agree with ${mode} usage`, async ({ page }) => { + const reverted = mode === "rollback" + let settled = mode !== "live" + const transport = await installSseTransport(page, { + server: `http://127.0.0.1:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`, + path: `/api/session/${sessionID}/event`, + }) await mockOpenCodeServer(page, { directory, project: { id: "proj_context_usage", worktree: directory, time: { created: 1, updated: 1 }, sandboxes: [] }, @@ -57,20 +65,50 @@ for (const reverted of [false, true]) { ...(reverted ? { revert: { messageID: "msg_3_user", inclusive: true } } : {}), }, ], - currentPageMessages: () => ({ items: messages.toReversed(), throughSeq: 0 }), + currentPageMessages: () => ({ + items: messages + .map((message) => + !settled && message.type === "assistant" && message.id === "msg_4_assistant" + ? { + ...message, + time: { created: 4 }, + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + } + : message, + ) + .toReversed(), + throughSeq: 0, + }), }) await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) await expectSessionTitle(page, "Context usage regression") const usage = page.getByRole("button", { name: "View context usage", exact: true }).first() const percentage = reverted ? 25 : 50 - await expect - .poll(async () => - usage.locator('circle[data-slot$="-progress"]').evaluate((circle) => { - const total = Number(circle.getAttribute("stroke-dasharray")) - return Math.round(100 * (1 - Number(circle.getAttribute("stroke-dashoffset")) / total)) + const expectPercentage = (value: number) => + expect + .poll(async () => + usage.locator('circle[data-slot$="-progress"]').evaluate((circle) => { + const total = Number(circle.getAttribute("stroke-dasharray")) + return Math.round(100 * (1 - Number(circle.getAttribute("stroke-dashoffset")) / total)) + }), + ) + .toBe(value) + await expectPercentage(mode === "live" ? 25 : percentage) + if (mode === "live") { + settled = true + await transport.send( + event("session.next.step.ended", { + sessionID, + timestamp: 10, + assistantMessageID: "msg_4_assistant", + finish: "stop", + cost: 1.25, + tokens: { input: 40_000, output: 10_000, reasoning: 0, cache: { read: 0, write: 0 } }, }), ) - .toBe(percentage) + await expectPercentage(50) + } await usage.hover() await expect(page.getByRole("tooltip")).toContainText(`${percentage}%`) await expect(page.getByRole("tooltip")).toContainText((percentage * 1_000).toLocaleString("en-US")) diff --git a/packages/app/e2e/regression/session-pending-reload.spec.ts b/packages/app/e2e/regression/session-pending-reload.spec.ts index 112a65fcdd36..0df822c14e7d 100644 --- a/packages/app/e2e/regression/session-pending-reload.spec.ts +++ b/packages/app/e2e/regression/session-pending-reload.spec.ts @@ -6,7 +6,7 @@ import { expectSessionTitle } from "../utils/waits" const directory = "C:/OpenCode/PendingReload" const sessionID = "ses_pending_reload" -test("keeps stopped steering visible across page reload without resuming inference", async ({ page }) => { +test.beforeEach(async ({ page }) => { await mockOpenCodeServer(page, { directory, project: { @@ -38,6 +38,9 @@ test("keeps stopped steering visible across page reload without resuming inferen pending: [{ id: "msg_pending", type: "user", text: "Keep this admitted steering input", time: { created: 1 } }], }), }) +}) + +test("keeps stopped steering visible across page reload without resuming inference", async ({ page }) => { await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) await expectSessionTitle(page, "Stopped steering") await expect(page.getByText("Keep this admitted steering input", { exact: true })).toHaveCount(1) @@ -46,3 +49,32 @@ test("keeps stopped steering visible across page reload without resuming inferen await expect(page.getByText("Keep this admitted steering input", { exact: true })).toHaveCount(1) await expect(page.getByRole("button", { name: "Stop", exact: true })).toHaveCount(0) }) + +test("stop settles before the first provider turn without removing admitted steering", async ({ page }) => { + let active = true + let pauses = 0 + await page.route("**/api/session/active", (route) => + route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ data: active ? { [sessionID]: { type: "running" } } : {} }), + }), + ) + await page.route(`**/api/session/${sessionID}/interrupt`, (route) => { + active = false + return route.fulfill({ status: 204 }) + }) + page.on("request", (request) => { + if (request.url().includes("queue/drain-pause")) pauses++ + }) + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await expectSessionTitle(page, "Stopped steering") + await expect(page.getByText("Keep this admitted steering input", { exact: true })).toHaveCount(1) + await page.getByRole("button", { name: "Stop", exact: true }).click() + await expect.poll(() => active).toBe(false) + await expect(page.getByRole("button", { name: "Stop", exact: true })).toHaveCount(0) + expect(pauses).toBe(1) + await expect(page.getByText("Keep this admitted steering input", { exact: true })).toHaveCount(1) + await page.reload() + await expect(page.getByText("Keep this admitted steering input", { exact: true })).toHaveCount(1) + await expect(page.getByRole("button", { name: "Stop", exact: true })).toHaveCount(0) +}) diff --git a/packages/app/e2e/regression/session-request-docks.spec.ts b/packages/app/e2e/regression/session-request-docks.spec.ts index 834aac918e85..f1c07ca7bc7d 100644 --- a/packages/app/e2e/regression/session-request-docks.spec.ts +++ b/packages/app/e2e/regression/session-request-docks.spec.ts @@ -107,7 +107,7 @@ test("shows a pending permission dock", async ({ page }) => { test("restores the draft caret before typing after a request dock closes", async ({ page }) => { const transport = await installSseTransport(page, { server: `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`, - path: "/global/event", + path: "/api/event", retry: 20, }) await mockServer(page, { questions: [] }) @@ -134,21 +134,21 @@ test("restores the draft caret before typing after a request dock closes", async ) .toBe(cursor) await transport.send({ - directory, - payload: { - type: "question.asked", - properties: { - id: "question-caret", - sessionID, - questions: [ - { - header: "Continue", - question: "Continue?", - options: [{ label: "Yes", description: "Continue the session" }], - }, - ], - tool: { messageID: "message-caret", callID: "call-caret" }, - }, + id: "evt_question_caret", + created: Date.now(), + location: { directory }, + type: "question.v2.asked", + data: { + id: "question-caret", + sessionID, + questions: [ + { + header: "Continue", + question: "Continue?", + options: [{ label: "Yes", description: "Continue the session" }], + }, + ], + tool: { messageID: "msg_caret", callID: "call-caret" }, }, }) const question = page.locator('[data-component="dock-prompt"][data-kind="question"]') @@ -156,8 +156,11 @@ test("restores the draft caret before typing after a request dock closes", async await expect(editor).toHaveCount(0) await transport.send({ - directory, - payload: { type: "question.rejected", properties: { sessionID, requestID: "question-caret" } }, + id: "evt_question_caret_rejected", + created: Date.now(), + location: { directory }, + type: "question.v2.rejected", + data: { sessionID, requestID: "question-caret" }, }) await expect(question).toHaveCount(0) await expect(editor).toBeVisible() diff --git a/packages/app/e2e/regression/session-rollback-queue.spec.ts b/packages/app/e2e/regression/session-rollback-queue.spec.ts index c3256bfbd580..80880719e8cc 100644 --- a/packages/app/e2e/regression/session-rollback-queue.spec.ts +++ b/packages/app/e2e/regression/session-rollback-queue.spec.ts @@ -153,3 +153,48 @@ test("does not stage rollback when inference interruption fails", async ({ page await expect(input).toContainText("Keep my existing draft") await expect(page.locator('[data-component="session-revert-dock"]')).toHaveCount(0) }) + +test("command undo and redo use the current pending input and replacement draft", async ({ page }) => { + const stages: unknown[] = [] + let cleared = 0 + await mockOpenCodeServer(page, { + directory, + findFiles: () => [], + fileList: () => [], + project: { id: projectID, worktree: directory, time: { created: 1, updated: 1 }, sandboxes: [] }, + provider: { + all: [{ id: "opencode", name: "OpenCode", models: { "test-model": { id: "test-model", name: "Test Model" } } }], + connected: ["opencode"], + default: model, + }, + sessions: [{ ...session }], + currentPageMessages: () => ({ + items: messages.slice(0, 2).toReversed(), + throughSeq: 5, + pending: [messages[2]!], + }), + }) + page.on("request", (request) => { + if (request.url().endsWith("/revert/stage")) stages.push(request.postDataJSON()) + if (request.url().endsWith("/revert/clear")) cleared++ + }) + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await expectSessionTitle(page, session.title) + await expect(page.getByText("second user prompt", { exact: true })).toHaveCount(1) + const select = async (command: string) => { + await page.keyboard.press("Control+p") + const dialog = page.getByRole("dialog") + await dialog.getByRole("textbox").fill(command) + await dialog.getByText(command, { exact: true }).click() + } + const input = page.locator('[data-component="prompt-input"]') + await select("Undo") + await expect(input).toContainText("second user prompt") + await expect(page.locator('[data-component="session-revert-dock"]')).toContainText("second user prompt") + expect(stages).toEqual([{ messageID: "msg_user_0002", inclusive: true }]) + await select("Redo") + await expect.poll(() => cleared).toBe(1) + await expect(input).toHaveText("") + await expect(page.locator('[data-component="session-revert-dock"]')).toHaveCount(0) + await expect(page.getByText("second user prompt", { exact: true })).toHaveCount(1) +}) diff --git a/packages/app/e2e/regression/session-timeline-collapse-state.spec.ts b/packages/app/e2e/regression/session-timeline-collapse-state.spec.ts index c2a6ee467a94..600ee1bb1749 100644 --- a/packages/app/e2e/regression/session-timeline-collapse-state.spec.ts +++ b/packages/app/e2e/regression/session-timeline-collapse-state.spec.ts @@ -10,7 +10,7 @@ const sessionID = "ses_timeline_state_regression" const userMessageID = "msg_user_regression" const assistantMessageID = "msg_assistant_regression" const editPartID = "prt_0001_edit" -const textPartID = "prt_9999_text" +const textPartID = `${assistantMessageID}:text:0` const title = "Timeline collapse state regression" const model = { providerID: "opencode", modelID: "claude-opus-4-6", variant: "max" } @@ -160,7 +160,7 @@ test.describe("regression: session timeline local row state", () => { expect(siblingProbe).toEqual({ fileMarker: "before", frameMarker: "before", - rowKey: `assistant-part:${userMessageID}:current:${assistantMessageID}:${editPartID}`, + rowKey: `assistant-part:${userMessageID}:part:${assistantMessageID}:${editPartID}`, rowMarker: "before", shadowRoots: 0, toolMarker: "before", @@ -187,7 +187,7 @@ test.describe("regression: session timeline local row state", () => { expect(await readDiffProbe(page)).toEqual({ fileMarker: "before", frameMarker: "before", - rowKey: `assistant-part:${userMessageID}:current:${assistantMessageID}:${editPartID}`, + rowKey: `assistant-part:${userMessageID}:part:${assistantMessageID}:${editPartID}`, rowMarker: "before", shadowRoots: 0, toolMarker: "before", diff --git a/packages/app/e2e/regression/session-timeline-context-resize.spec.ts b/packages/app/e2e/regression/session-timeline-context-resize.spec.ts index a01d88b0d955..c8d5d5c17b37 100644 --- a/packages/app/e2e/regression/session-timeline-context-resize.spec.ts +++ b/packages/app/e2e/regression/session-timeline-context-resize.spec.ts @@ -25,7 +25,7 @@ const sessionID = "ses_context_resize_regression" const title = "Context resize regression" const model = { providerID: "opencode", modelID: "claude-opus-4-6", variant: "max" } const contextIDs = ["prt_0100_read", "prt_0101_glob", "prt_0102_grep", "prt_0103_list"] -const followingTextID = "prt_0104_text" +const followingTextID = "msg_assistant_0010:text:0" const messages = [...Array.from({ length: 8 }, (_, index) => turn(index, false)).flat(), ...turn(10, true)] @@ -232,19 +232,9 @@ function turn(index: number, target: boolean, status: "running" | "completed" = assistantMessage( target ? [ - contextTool( - contextIDs[0]!, - "read", - { filePath: "src/recent-a.ts", offset: 0, limit: 120 }, - status, - ), + contextTool(contextIDs[0]!, "read", { filePath: "src/recent-a.ts", offset: 0, limit: 120 }, status), contextTool(contextIDs[1]!, "glob", { path: directory, pattern: "**/*.ts" }, status), - contextTool( - contextIDs[2]!, - "grep", - { path: directory, pattern: "Explored", include: "*.ts" }, - status, - ), + contextTool(contextIDs[2]!, "grep", { path: directory, pattern: "Explored", include: "*.ts" }, status), contextTool(contextIDs[3]!, "list", { path: "src" }, status), textPart(followingTextID, "This assistant text is immediately after the explored context group."), ] @@ -265,11 +255,7 @@ function contextTool( return toolPart(partID, tool, status, input, { title, output: `Completed ${tool}.\n${"detail line\n".repeat(8)}` }) } -async function mockServer( - page: Page, - events: TimelineEvent[] = [], - fixtureMessages = messages, -) { +async function mockServer(page: Page, events: TimelineEvent[] = [], fixtureMessages = messages) { await mockOpenCodeServer(page, { directory, project: project(), diff --git a/packages/app/e2e/regression/session-timeline-history-root.spec.ts b/packages/app/e2e/regression/session-timeline-history-root.spec.ts index 48f71ecc4cc0..72b0812a8ffd 100644 --- a/packages/app/e2e/regression/session-timeline-history-root.spec.ts +++ b/packages/app/e2e/regression/session-timeline-history-root.spec.ts @@ -12,6 +12,7 @@ import { title, userID, userMessage, + type TimelineEvent, } from "../performance/timeline-stability/fixture" import { mockOpenCodeServer } from "../utils/mock-server" import { installSseTransport } from "../utils/sse-transport" @@ -33,8 +34,8 @@ const olderAssistant = assistantMessage([textPart(`prt_history_root_99`, "Earlie }) const messages = [olderUser, olderAssistant, userMessage(), ...latestAssistants] const lastAssistant = latestAssistants.at(-1)! -const lastPartID = lastAssistant.content[0]!.id -const olderPartID = olderAssistant.content[0]!.id +const lastPartID = `${lastAssistant.id}:text:0` +const olderPartID = `${olderAssistant.id}:text:0` const completed = { ...lastAssistant, time: { ...lastAssistant.time, completed: lastAssistant.time.created + 15_000 }, @@ -57,7 +58,7 @@ for (const scenario of scenarios) { const pages: { cursor?: string; limit: number }[] = [] const sequence: string[] = [] const history = Promise.withResolvers() - const transport = await installSseTransport<{ directory: string; payload: Record }>(page, { + const transport = await installSseTransport(page, { server: `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`, path: `/api/session/${sessionID}/event`, retry: 20, @@ -86,7 +87,7 @@ for (const scenario of scenarios) { sessions: [session()], status: active, currentPageMessages: (_, limit, cursor) => { - pages.push({ cursor, limit }) + if (limit === historyPageSize) pages.push({ cursor, limit }) const end = cursor ? Number(cursor) : messages.length const start = Math.max(0, end - limit) return { @@ -97,7 +98,9 @@ for (const scenario of scenarios) { }, }) await page.route(`**/api/session/${sessionID}/message?**`, async (route) => { - const cursor = new URL(route.request().url()).searchParams.get("cursor") ?? undefined + const url = new URL(route.request().url()) + if (Number(url.searchParams.get("limit")) !== historyPageSize) return route.fallback() + const cursor = url.searchParams.get("cursor") ?? undefined const label = cursor ?? "latest" requests.push({ cursor, phase: "start" }) sequence.push(`messages:start:${label}`) @@ -162,8 +165,11 @@ for (const scenario of scenarios) { const viewport = page .locator(".scroll-view__viewport") .filter({ has: page.locator("[data-timeline-virtual-content]") }) - await viewport.hover() - await page.mouse.wheel(0, -100_000) + await viewport.evaluate((element) => { + element.dispatchEvent(new WheelEvent("wheel", { deltaY: -120, bubbles: true })) + element.scrollTop = 0 + element.dispatchEvent(new Event("scroll")) + }) await expect.poll(() => requests.filter((request) => request.phase === "start").length).toBe(2) expect(requests.filter((request) => request.phase === "end")).toHaveLength(1) const root = page.locator(`[data-message-id="${userID}"]`).first() @@ -190,9 +196,7 @@ for (const scenario of scenarios) { ).__historyRootProbe!.arm() }) await waitForProbeSamples(page, 0) - await expect - .poll(async () => visibleContentHidden(page), { timeout: 5_000 }) - .toBe(false) + await expect.poll(async () => visibleContentHidden(page), { timeout: 5_000 }).toBe(false) const beforeHistory = await probeSamples(page) history.resolve() await expect(root).toBeVisible() @@ -211,7 +215,7 @@ for (const scenario of scenarios) { for (const event of events) { const beforeEvent = await probeSamples(page) if (event === idle) active[sessionID] = { type: "idle" } - await transport.send(event) + await transport.burst(Array.isArray(event) ? event : [event]) if (event === events.at(-1)) await expect(page.getByRole("button", { name: "Stop" })).toHaveCount(0) await waitForProbeSamples(page, beforeEvent) const current = await timelineState(page) diff --git a/packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts b/packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts index e089538411be..a0d3e59a5a1b 100644 --- a/packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts +++ b/packages/app/e2e/regression/session-timeline-lifecycle-state.spec.ts @@ -48,13 +48,13 @@ test("shows and expands a running shell command without shimmering it", async ({ await expect(tool.locator('[data-slot="bash-pre"]')).toContainText("still running") }) -test("transitions thinking and hidden reasoning through busy to idle", async ({ page }) => { - const reasoningID = "prt_reasoning_hidden" +test("transitions thinking and hidden reasoning through busy to idle", async ({ page, browserName }) => { + const reasoningID = "msg_1001_timeline_assistant:reasoning:0" const assistant = assistantMessage([reasoningPart(reasoningID, "## Inspecting stability")], { completed: false }) const timeline = await setupTimeline(page, { messages: [userMessage(), assistant], settings: { showReasoningSummaries: false }, - cpuRate: 4, + cpuRate: browserName === "chromium" ? 4 : undefined, }) await timeline.sendStatus("busy", 150) @@ -91,7 +91,10 @@ test("moves busy through retry and recovery to final idle content", async ({ pag await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() await expect(page.locator('[data-timeline-row="DiffSummary"]')).toHaveCount(0) await timeline.sendStatus("retry", 180) - await expect(page.locator('[data-timeline-row="Retry"]')).toBeVisible() + await expect(page.locator('[data-timeline-row="Retry"]')).toContainText("Rate limited") + await expect(page.locator('[data-timeline-row="Retry"]')).toContainText("attempt #1") + await timeline.sendStatus("retry", 100, 2) + await expect(page.locator('[data-timeline-row="Retry"]')).toContainText("attempt #2") await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0) await timeline.sendStatus("busy", 180, 2) await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible() diff --git a/packages/app/e2e/regression/session-timeline-projection.spec.ts b/packages/app/e2e/regression/session-timeline-projection.spec.ts index dda62a10abcc..9f580f2d45e0 100644 --- a/packages/app/e2e/regression/session-timeline-projection.spec.ts +++ b/packages/app/e2e/regression/session-timeline-projection.spec.ts @@ -99,11 +99,10 @@ test.describe("session timeline projection", () => { }) const nextAssistant = assistantMessage([{ id: "prt_second_text", type: "text", text: "Second response" }], { id: "msg_2001_second_assistant", - parentID: "msg_2000_second_user", created: 1700000006000, }) const timeline = await setupTimeline(page, { - messages: [firstUser, aborted, compactionMessage(), failed, nextUser, nextAssistant], + messages: [firstUser, aborted, failed, nextUser, compactionMessage({ created: 1700000005001 }), nextAssistant], }) await timeline.sendStatus("idle", 100) const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") }) @@ -134,7 +133,6 @@ test.describe("session timeline projection", () => { const nextUser = userMessage(undefined, { id: "msg_2000_diff_next_user", created: 1700000010000 }) const nextAssistant = assistantMessage([], { id: "msg_2001_diff_next_assistant", - parentID: "msg_2000_diff_next_user", created: 1700000011000, }) await setupTimeline(page, { diff --git a/packages/app/e2e/regression/session-timeline-reducer-projection.spec.ts b/packages/app/e2e/regression/session-timeline-reducer-projection.spec.ts index 2af4892f32cb..8f495bde4094 100644 --- a/packages/app/e2e/regression/session-timeline-reducer-projection.spec.ts +++ b/packages/app/e2e/regression/session-timeline-reducer-projection.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "@playwright/test" import { assistantMessage, + assistantID, completedAssistantInfo, messageUpdated, partUpdated, @@ -29,7 +30,7 @@ test("groups singleton and separated context operations at correct boundaries", }) test("reducer-hardening: converges when idle arrives before final part and message completion", async ({ page }) => { - const textID = "prt_event_order_text" + const textID = `${assistantID}:text:0` const assistant = assistantMessage([textPart(textID, "Partial")], { completed: false }) const timeline = await setupTimeline(page, { messages: [userMessage(), assistant] }) // Hydrate already marks the incomplete assistant as busy; only force early idle. diff --git a/packages/app/e2e/regression/session-timeline-shell-outline.spec.ts b/packages/app/e2e/regression/session-timeline-shell-outline.spec.ts index 209ed1656429..0d67f3f36446 100644 --- a/packages/app/e2e/regression/session-timeline-shell-outline.spec.ts +++ b/packages/app/e2e/regression/session-timeline-shell-outline.spec.ts @@ -71,8 +71,11 @@ for (const deviceScaleFactor of [1.25, 1.5]) { test("keeps the patch card inside a fractionally short virtual row", async ({ page }) => { const patchID = "prt_patch_outline" const file = { - file: "src/outline.ts", - status: "modified", + filePath: "src/outline.ts", + relativePath: "src/outline.ts", + type: "update", + before: "const outline = false\n", + after: "const outline = true\n", additions: 1, deletions: 1, patch: "@@ -1 +1 @@\n-const outline = false\n+const outline = true", @@ -81,7 +84,7 @@ test("keeps the patch card inside a fractionally short virtual row", async ({ pa messages: [ userMessage(), assistantMessage([ - toolPart(patchID, "apply_patch", "completed", { files: [file.file] }, { metadata: { files: [file] } }), + toolPart(patchID, "apply_patch", "completed", { files: [file.filePath] }, { metadata: { files: [file] } }), ]), ], settings: { editToolPartsExpanded: true, newLayoutDesigns: true }, @@ -142,7 +145,6 @@ test("allows paint rounding for every framed row but not fixed turn gaps", async userMessage(undefined, { id: secondUserID, created: 1700000010000 }), assistantMessage([], { id: "msg_outline_second_assistant", - parentID: secondUserID, created: 1700000011000, }), ], diff --git a/packages/app/e2e/regression/session-timeline-transport.spec.ts b/packages/app/e2e/regression/session-timeline-transport.spec.ts index 359804997876..6cd1b338b0e5 100644 --- a/packages/app/e2e/regression/session-timeline-transport.spec.ts +++ b/packages/app/e2e/regression/session-timeline-transport.spec.ts @@ -4,48 +4,52 @@ import { partUpdated, setupTimeline, textPart, + timelineEvents, userMessage, } from "../performance/timeline-stability/fixture" test("keeps one connection open while delivering multiple events", async ({ page }) => { const timeline = await setupTimeline(page) - const first = await timeline.transport.send(partUpdated(textPart("prt_transport_first", "first event"))) - const second = await timeline.transport.send(partUpdated(textPart("prt_transport_second", "second event"))) + const first = await timeline.transport.burst( + timelineEvents(partUpdated(textPart("prt_transport_first", "first event"))), + ) + const second = await timeline.transport.burst( + timelineEvents(partUpdated(textPart("prt_transport_second", "second event"))), + ) - await timeline.waitForPart("prt_transport_first") - await timeline.waitForPart("prt_transport_second") - expect(first.connectionID).toBe(second.connectionID) + await expect(page.getByText("first event", { exact: true })).toBeVisible() + await expect(page.getByText("second event", { exact: true })).toBeVisible() + expect(first[0]!.connectionID).toBe(second[0]!.connectionID) await expect.poll(async () => (await timeline.transport.connections()).length).toBe(1) - expect(await timeline.transport.acknowledgements()).toHaveLength(2) + expect(await timeline.transport.acknowledgements()).toHaveLength(4) }) test("delivers a burst from one stream chunk", async ({ page }) => { const timeline = await setupTimeline(page) const acknowledgements = await timeline.transport.burst([ - partUpdated(textPart("prt_transport_burst_a", "burst a")), - partUpdated(textPart("prt_transport_burst_b", "burst b")), + ...timelineEvents(partUpdated(textPart("prt_transport_burst_a", "burst a"))), + ...timelineEvents(partUpdated(textPart("prt_transport_burst_b", "burst b"))), ]) - await timeline.waitForPart("prt_transport_burst_a") - await timeline.waitForPart("prt_transport_burst_b") - expect(acknowledgements.map((item) => item.chunkCount)).toEqual([1, 1]) - expect(new Set(acknowledgements.map((item) => item.deliveryID)).size).toBe(2) + await expect(page.getByText("burst a", { exact: true })).toBeVisible() + await expect(page.getByText("burst b", { exact: true })).toBeVisible() + expect(acknowledgements.map((item) => item.chunkCount)).toEqual([1, 1, 1, 1]) + expect(new Set(acknowledgements.map((item) => item.deliveryID)).size).toBe(4) }) test("parses split JSON and a split multibyte code point", async ({ page }) => { const timeline = await setupTimeline(page) - const payload = partUpdated(textPart("prt_transport_split", "split snowman \u2603\u2603\u2603")) + const events = timelineEvents(partUpdated(textPart("prt_transport_split", "split snowman \u2603\u2603\u2603"))) + await timeline.transport.send(events[0]!) + const payload = events[1]! const encoded = new TextEncoder().encode(`data: ${JSON.stringify(payload)}\n\n`) const snowman = new TextEncoder().encode("\u2603")[0]! const multibyte = encoded.indexOf(snowman) const acknowledgement = await timeline.transport.split(payload, [9, multibyte + 1, multibyte + 2]) - await timeline.waitForPart("prt_transport_split") - await expect(page.locator('[data-timeline-part-id="prt_transport_split"]')).toContainText( - "split snowman \u2603\u2603\u2603", - ) + await expect(page.getByText("split snowman \u2603\u2603\u2603", { exact: true })).toBeVisible() expect(acknowledgement.chunkCount).toBe(4) }) @@ -54,19 +58,16 @@ test("delivers server heartbeat without mutating the timeline", async ({ page }) const timeline = await setupTimeline(page, { messages: [userMessage(), assistantMessage([textPart("prt_transport_steady", "steady")])], }) - await timeline.waitForPart("prt_transport_steady") + await expect(page.locator('[data-component="markdown"]').getByText("steady", { exact: true })).toBeVisible() const before = await stableTimelineRows(page) await timeline.transport.writeRaw(": heartbeat\n\n") - await timeline.transport.send(partUpdated(textPart(sentinelID, "heartbeat processed"))) - await timeline.waitForPart(sentinelID) + expect(await stableTimelineRows(page)).toEqual(before) + + // A real append may move the assistant footer; check heartbeat stability before it. + await timeline.send(partUpdated(textPart(sentinelID, "heartbeat processed"))) + await expect(page.getByText("heartbeat processed", { exact: true })).toBeVisible() - await expect - .poll(async () => { - const rows = await timelineRows(page) - return rows.filter((row) => before.some((item) => item.key === row.key)) - }) - .toEqual(before) await expect.poll(async () => (await timeline.transport.connections()).length).toBe(1) }) @@ -76,9 +77,9 @@ test("reconnects after a clean close", async ({ page }) => { await timeline.transport.close() const second = await timeline.transport.waitForConnection({ after: first.id }) - await timeline.transport.send(partUpdated(textPart("prt_transport_close", "after close"))) + await timeline.send(partUpdated(textPart("prt_transport_close", "after close"))) - await timeline.waitForPart("prt_transport_close") + await expect(page.getByText("after close", { exact: true })).toBeVisible() expect(second.id).toBeGreaterThan(first.id) expect((await timeline.transport.connections())[0]?.endedBy).toBe("close") }) @@ -89,24 +90,25 @@ test("reconnects after a stream error", async ({ page }) => { await timeline.transport.error("contract failure") const second = await timeline.transport.waitForConnection({ after: first.id }) - await timeline.transport.send(partUpdated(textPart("prt_transport_error", "after error"))) + await timeline.send(partUpdated(textPart("prt_transport_error", "after error"))) - await timeline.waitForPart("prt_transport_error") + await expect(page.getByText("after error", { exact: true })).toBeVisible() await expect.poll(async () => (await timeline.transport.connections()).length).toBe(2) expect(second.id).toBeGreaterThan(first.id) expect((await timeline.transport.connections())[0]?.endedBy).toBe("error") }) -test("does not request replay when reconnecting the volatile V2 event stream", async ({ page }) => { +test("resumes the durable session stream after its last received sequence", async ({ page }) => { const timeline = await setupTimeline(page, { protocol: "v2" }) - const first = await timeline.transport.send(partUpdated(textPart("prt_transport_id", "event with id")), { - id: "timeline-event-7", - }) - await timeline.waitForPart("prt_transport_id") + const events = timelineEvents(partUpdated(textPart("prt_transport_id", "event with id"))) + await timeline.transport.send(events[0]!) + const first = await timeline.transport.send(events[1]!, { id: "timeline-event-7" }) + await expect(page.getByText("event with id", { exact: true })).toBeVisible() await timeline.transport.error("retry with event id") const connection = await timeline.transport.waitForConnection({ after: first.connectionID }) + expect(new URL(connection.url).searchParams.get("after")).toBe(String(events[1]!.durable?.seq)) expect(first.eventID).toBe("timeline-event-7") expect(connection.headers["last-event-id"]).toBeUndefined() }) @@ -148,7 +150,7 @@ function timelineRows(page: Page) { parts: Array.from(element.querySelectorAll("[data-timeline-part-id]"), (part) => part.getAttribute("data-timeline-part-id"), ), - text: element.textContent, + text: (element as HTMLElement).innerText.replace(/\s+/g, " ").trim(), })), ) } diff --git a/packages/app/e2e/regression/session-todo-dock-navigation.spec.ts b/packages/app/e2e/regression/session-todo-dock-navigation.spec.ts index 55e71212753c..c7f4d2110df9 100644 --- a/packages/app/e2e/regression/session-todo-dock-navigation.spec.ts +++ b/packages/app/e2e/regression/session-todo-dock-navigation.spec.ts @@ -1,6 +1,7 @@ import { base64Encode } from "@opencode-ai/core/util/encode" import { expect, test, type Page } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" +import { installSseTransport } from "../utils/sse-transport" import { expectSessionTitle } from "../utils/waits" const directory = "C:/OpenCode/TodoDockNavigation" @@ -17,17 +18,23 @@ const activeTodos = [ ] type EventPayload = { - directory: string - payload: Record + id: string + created: number + location: { directory: string } + type: string + data: Record } -test.use({ viewport: { width: 1440, height: 900 }, reducedMotion: "no-preference" }) +test.use({ viewport: { width: 1440, height: 900 }, contextOptions: { reducedMotion: "no-preference" } }) test("animates todo lifecycle without replaying it across session tabs", async ({ page }) => { test.setTimeout(90_000) - const events: EventPayload[] = [] + const transport = await installSseTransport(page, { + server: `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`, + path: "/api/event", + }) const todos: Record = { [sourceID]: [], [otherID]: [] } - const sessionStatus: Record = {} + const sessionStatus: Record = { [sourceID]: { type: "busy" } } await mockOpenCodeServer(page, { directory, @@ -57,10 +64,30 @@ test("animates todo lifecycle without replaying it across session tabs", async ( default: { providerID: "opencode", modelID: "claude-opus-4-6" }, }, sessions: [session(sourceID, sourceTitle, 1700000000000), session(otherID, otherTitle, 1700000001000)], - sessionStatus: { [sourceID]: { type: "busy" } }, - pageMessages: () => ({ items: [] }), - events: () => events.splice(0, 1), - eventRetry: 16, + currentPageMessages: (id) => ({ + items: + id === sourceID + ? [ + { + id: "msg_todo_assistant", + type: "assistant", + agent: "build", + model: { providerID: "opencode", id: "claude-opus-4-6" }, + time: { created: 1700000000001 }, + content: [{ id: "txt_todo", type: "text", text: "Working on the task list" }], + }, + { + id: "msg_todo_user", + type: "user", + text: "Track these tasks", + files: [], + agents: [], + time: { created: 1700000000000 }, + }, + ] + : [], + throughSeq: 0, + }), sessionStatus: () => sessionStatus, todos: (sessionID) => todos[sessionID] ?? [], }) @@ -68,17 +95,16 @@ test("animates todo lifecycle without replaying it across session tabs", async ( await page.goto(sessionHref(sourceID)) await expectSessionTitle(page, sourceTitle) + await transport.waitForConnection() const dock = page.locator('[data-component="session-todo-dock"]') await expect(dock).toHaveCount(0) - sessionStatus[sourceID] = { type: "busy" } - events.push(statusEvent(sourceID, "busy")) await expect(page.getByRole("button", { name: "Stop" })).toBeVisible() await page.waitForTimeout(700) const opening = sampleDock(page, 1_000) todos[sourceID] = activeTodos - events.push(todoEvent(sourceID, activeTodos)) + await transport.send(todoEvent(sourceID, activeTodos)) await expect(dock).toBeVisible() await expect(dock.locator('[data-state="in_progress"]')).toHaveCount(1) expect((await opening).some((sample) => sample.opacity > 0.05 && sample.opacity < 0.95)).toBe(true) @@ -97,11 +123,12 @@ test("animates todo lifecycle without replaying it across session tabs", async ( const completedTodos = activeTodos.map((todo) => ({ ...todo, status: "completed" })) const closing = sampleDock(page, 1_000) todos[sourceID] = completedTodos - events.push(todoEvent(sourceID, completedTodos)) + await transport.send(todoEvent(sourceID, completedTodos)) await expect(dock).toHaveCount(0) - expect((await closing).some((sample) => sample.opacity > 0.05 && sample.opacity < 0.95)).toBe(true) + const closeSamples = await closing + expect(closeSamples.some((sample) => sample.opacity > 0.05 && sample.opacity < 0.95)).toBe(true) todos[sourceID] = [] - events.push(todoEvent(sourceID, [])) + await transport.send(todoEvent(sourceID, [])) await switchSession(page, otherID, otherTitle) const returningEmpty = sampleDock(page, 700) @@ -122,17 +149,13 @@ function session(id: string, title: string, created: number) { } } -function statusEvent(sessionID: string, type: "busy" | "idle"): EventPayload { - return { - directory, - payload: { type: "session.status", properties: { sessionID, status: { type } } }, - } -} - function todoEvent(sessionID: string, next: typeof activeTodos): EventPayload { return { - directory, - payload: { type: "todo.updated", properties: { sessionID, todos: next } }, + id: `evt_todo_${Date.now()}`, + created: Date.now(), + location: { directory }, + type: "todo.updated", + data: { sessionID, todos: next }, } } diff --git a/packages/app/e2e/regression/subagent-child-navigation.spec.ts b/packages/app/e2e/regression/subagent-child-navigation.spec.ts index 2512da6f2e38..8c35827c7b85 100644 --- a/packages/app/e2e/regression/subagent-child-navigation.spec.ts +++ b/packages/app/e2e/regression/subagent-child-navigation.spec.ts @@ -20,7 +20,7 @@ test("navigates to a subagent child session missing from the session list", asyn await setup(page) await openChildFromParent(page) - await expectSessionTitle(page, taskDescription) + await expect(page.getByRole("heading", { name: taskDescription, exact: true })).toBeVisible() await expect(page.getByRole("heading", { name: parentTitle })).toHaveCount(0) const titlebarRight = page.locator("#opencode-titlebar-right") @@ -31,7 +31,7 @@ test("shows the not found fallback when the viewed session is deleted", async ({ const events: EventPayload[] = [] await setup(page, () => events.splice(0, 1)) await openChildFromParent(page) - await expectSessionTitle(page, taskDescription) + await expect(page.getByRole("heading", { name: taskDescription, exact: true })).toBeVisible() events.push({ directory, diff --git a/packages/app/e2e/smoke/session-timeline.fixture.ts b/packages/app/e2e/smoke/session-timeline.fixture.ts index 0867f6df28e4..f36562e195b5 100644 --- a/packages/app/e2e/smoke/session-timeline.fixture.ts +++ b/packages/app/e2e/smoke/session-timeline.fixture.ts @@ -1,3 +1,5 @@ +import { sessionMessagePartID } from "../../src/utils/session-message" + const words = [ "alpha", "bravo", @@ -21,7 +23,7 @@ const words = [ "vector", ] -const serverKey = "http://127.0.0.1:4096" +const serverKey = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}` const sourceID = "ses_smoke_source" const targetID = "ses_smoke_target" const directory = "C:/OpenCode/SmokeProject" @@ -239,9 +241,14 @@ function renderable(part: MessagePart) { } function orderedParts(message: Message) { - if (message.type === "user") return [{ id: message.id, type: "user" }] + if (message.type === "user") return [{ id: sessionMessagePartID(message.id, "text", 0), type: "user" }] // Match native timeline projection: content array order, not id-sort. - return message.content.slice() + const ordinals = { text: 0, reasoning: 0 } + return message.content.map((part) => + part.type === "text" || part.type === "reasoning" + ? { ...part, id: sessionMessagePartID(message.id, part.type, ordinals[part.type]++) } + : part, + ) } export const fixture = { @@ -286,8 +293,8 @@ export const fixture = { time: { created: 1700000001000, updated: 1700000001000 }, }, ], - sourceID, - targetID, + sourceID: sourceID as typeof sourceID, + targetID: targetID as typeof targetID, messages: { [sourceID]: sourceMessages, [targetID]: targetMessages }, expected: { sourceTitle: "Uncommitted changes inquiry", diff --git a/packages/app/e2e/smoke/session-timeline.spec.ts b/packages/app/e2e/smoke/session-timeline.spec.ts index 27e51b19f936..b1410cf4c7e7 100644 --- a/packages/app/e2e/smoke/session-timeline.spec.ts +++ b/packages/app/e2e/smoke/session-timeline.spec.ts @@ -32,33 +32,38 @@ test.describe("smoke: session timeline", () => { test.setTimeout(240_000) test("keeps the visible message fixed while prepending history", async ({ page }) => { - const requests: { before?: string; phase: "start" | "end"; at: number }[] = [] + const requests: { before?: string; phase: "start" | "end" }[] = [] + const history = Promise.withResolvers() await mockOpenCodeServer(page, { sessions: fixture.sessions, provider: fixture.provider, directory: fixture.directory, project: fixture.project, currentPageMessages, - messageDelay: 3_000, - onMessages: (input) => requests.push({ before: input.before, phase: input.phase, at: performance.now() }), + }) + await page.route(`**/api/session/${fixture.targetID}/message?**`, async (route) => { + const url = new URL(route.request().url()) + // Compatibility hydration also calls this endpoint with a smaller page size. + if (Number(url.searchParams.get("limit")) !== 100) return route.fallback() + const before = url.searchParams.get("cursor") ?? undefined + requests.push({ before, phase: "start" }) + if (before) await history.promise + await route.fallback() + requests.push({ before, phase: "end" }) }) await configureSmokePage(page, fixture.directory) await navigateToSession(page, fixture.directory, fixture.targetID, fixture.expected.targetTitle) await waitForTimelineStable(page) const scroller = timelineScroller(page) - await pointAtTimeline(page) - const deadline = Date.now() + 120_000 - while (!requests.some((request) => request.before && request.phase === "start")) { - if (Date.now() >= deadline) throw new Error("Timed out scrolling to the history boundary") - await page.mouse.wheel(0, -240) - await page.waitForTimeout(20) - } + await scroller.evaluate((element) => { + element.dispatchEvent(new WheelEvent("wheel", { deltaY: -120, bubbles: true })) + element.scrollTop = 0 + element.dispatchEvent(new Event("scroll")) + }) + await expect.poll(() => requests.some((request) => request.before && request.phase === "start")).toBe(true) expect(requests.some((request) => request.before && request.phase === "end")).toBe(false) - for (let index = 0; index < 12; index++) { - await page.mouse.wheel(0, -120) - await page.waitForTimeout(20) - } + await waitForTimelineStable(page) const keys = await scroller.evaluate((element) => { const view = element.getBoundingClientRect() return [...element.querySelectorAll("[data-timeline-part-id]")] @@ -85,6 +90,7 @@ test.describe("smoke: session timeline", () => { const before = await positions() expect(requests.some((request) => request.before && request.phase === "end")).toBe(false) + history.resolve() await expect.poll(() => requests.some((request) => request.before && request.phase === "end")).toBe(true) await waitForTimelineStable(page) await expect.poll(positions).toEqual(before) @@ -125,20 +131,25 @@ test.describe("smoke: session timeline", () => { }) await configureSmokePage(page, fixture.directory) await page.addInitScript( - ({ dirBase64, sourceID, targetID }) => { + ({ dirBase64, sourceID, targetID, server }) => { localStorage.setItem( "opencode.window.browser.dat:tabs", JSON.stringify( [sourceID, targetID].map((sessionId) => ({ type: "session", - server: "http://127.0.0.1:4096", + server, dirBase64, sessionId, })), ), ) }, - { dirBase64: base64Encode(fixture.directory), sourceID: fixture.sourceID, targetID: fixture.targetID }, + { + dirBase64: base64Encode(fixture.directory), + sourceID: fixture.sourceID, + targetID: fixture.targetID, + server: `http://127.0.0.1:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`, + }, ) await page.goto(`/${base64Encode(fixture.directory)}/session/${fixture.targetID}`) @@ -155,15 +166,22 @@ test.describe("smoke: session timeline", () => { const firstPaintNodes = new WeakSet() let firstPaint = false let removedFirstPaintNodes = 0 + const removedNodes: string[] = [] let running = true new MutationObserver((records) => { if (!firstPaint || !running) return records.forEach((record) => record.removedNodes.forEach((node) => { - if (firstPaintNodes.has(node)) removedFirstPaintNodes += 1 + if (firstPaintNodes.has(node)) { + removedFirstPaintNodes += 1 + removedNodes.push(node instanceof Element ? node.outerHTML.slice(0, 600) : node.nodeName) + } if (!(node instanceof Element)) return node.querySelectorAll("*").forEach((element) => { - if (firstPaintNodes.has(element)) removedFirstPaintNodes += 1 + if (firstPaintNodes.has(element)) { + removedFirstPaintNodes += 1 + removedNodes.push(element.outerHTML.slice(0, 600)) + } }) }), ) @@ -187,7 +205,11 @@ test.describe("smoke: session timeline", () => { const bottom = root .querySelector('[data-timeline-row="bottom-spacer"]') ?.getBoundingClientRect() - samples.push({ ids: visible, last: visible.includes(last), bottomError: bottom?.bottom - view.bottom }) + samples.push({ + ids: visible, + last: visible.includes(last), + bottomError: bottom ? bottom.bottom - view.bottom : undefined, + }) if (!firstPaint && visible.includes(last) && Math.abs((bottom?.bottom ?? Infinity) - view.bottom) <= 1) { firstPaint = true root.querySelectorAll("[data-timeline-key]").forEach((row) => { @@ -203,11 +225,17 @@ test.describe("smoke: session timeline", () => { } ;( window as Window & { - __sessionTabPaint?: { samples: typeof samples; removed: () => number; stop: () => void } + __sessionTabPaint?: { + samples: typeof samples + removed: () => number + removedNodes: string[] + stop: () => void + } } ).__sessionTabPaint = { samples, removed: () => removedFirstPaintNodes, + removedNodes, stop: () => { running = false }, @@ -230,16 +258,21 @@ test.describe("smoke: session timeline", () => { __sessionTabPaint?: { samples: Array<{ ids: string[]; last: boolean; bottomError?: number }> removed: () => number + removedNodes: string[] stop: () => void } } ).__sessionTabPaint! probe.stop() - return { first: probe.samples.find((sample) => sample.ids.length > 0), removed: probe.removed() } + return { + first: probe.samples.find((sample) => sample.ids.length > 0), + removed: probe.removed(), + removedNodes: probe.removedNodes, + } }) expect(first.first?.last).toBe(true) expect(Math.abs(first.first?.bottomError ?? Infinity)).toBeLessThanOrEqual(1) - expect(first.removed).toBe(0) + expect(first.removed, JSON.stringify(first.removedNodes)).toBe(0) }) test("paints a cold session tab at the latest message", async ({ page }) => { @@ -255,20 +288,25 @@ test.describe("smoke: session timeline", () => { }) await configureSmokePage(page, fixture.directory) await page.addInitScript( - ({ dirBase64, sourceID, targetID }) => { + ({ dirBase64, sourceID, targetID, server }) => { localStorage.setItem( "opencode.window.browser.dat:tabs", JSON.stringify( [sourceID, targetID].map((sessionId) => ({ type: "session", - server: "http://127.0.0.1:4096", + server, dirBase64, sessionId, })), ), ) }, - { dirBase64: base64Encode(fixture.directory), sourceID: fixture.sourceID, targetID: fixture.targetID }, + { + dirBase64: base64Encode(fixture.directory), + sourceID: fixture.sourceID, + targetID: fixture.targetID, + server: `http://127.0.0.1:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`, + }, ) await page.goto(`/${base64Encode(fixture.directory)}/session/${fixture.sourceID}`) await expectSessionTitle(page, fixture.expected.sourceTitle) @@ -314,8 +352,7 @@ test.describe("smoke: session timeline", () => { } ).__coldTabSamples return samples?.some( - (sample) => - sample.destination && sample.last && Math.abs(sample.bottomError ?? Infinity) <= 1, + (sample) => sample.destination && sample.last && Math.abs(sample.bottomError ?? Infinity) <= 1, ) }, { last: fixture.expected.targetMessageIDs.at(-1)! }, diff --git a/packages/app/e2e/tsconfig.json b/packages/app/e2e/tsconfig.json index 6c25dde7e933..4993860144a8 100644 --- a/packages/app/e2e/tsconfig.json +++ b/packages/app/e2e/tsconfig.json @@ -31,6 +31,19 @@ "./regression/remote-session-settings.spec.ts", "./regression/remote-tab-busy.spec.ts", "./regression/review-line-comment.spec.ts", - "./regression/tab-navigate-mousedown.spec.ts" + "./regression/tab-navigate-mousedown.spec.ts", + "./regression/session-request-docks.spec.ts", + "./regression/session-todo-dock-navigation.spec.ts", + "./regression/subagent-child-navigation.spec.ts", + "./regression/review-terminal-stacked.spec.ts", + "./regression/session-timeline-collapse-state.spec.ts", + "./regression/session-timeline-history-root.spec.ts", + "./regression/session-timeline-lifecycle-state.spec.ts", + "./regression/session-timeline-projection.spec.ts", + "./regression/session-timeline-reducer-projection.spec.ts", + "./regression/session-timeline-shell-outline.spec.ts", + "./regression/session-timeline-transport.spec.ts", + "./user-story/model-selection-flow.spec.ts", + "./smoke/session-timeline.spec.ts" ] } diff --git a/packages/app/e2e/user-story/model-selection-flow.spec.ts b/packages/app/e2e/user-story/model-selection-flow.spec.ts index 22b8bb41fe25..76581a5587e9 100644 --- a/packages/app/e2e/user-story/model-selection-flow.spec.ts +++ b/packages/app/e2e/user-story/model-selection-flow.spec.ts @@ -6,7 +6,6 @@ const directory = "C:/OpenCode/NewProject" test("creates a session in a new project, connects OpenCode Go, and selects its model", async ({ page }) => { let connectedGo = false - let pendingGo = false const connections: Array<{ integrationID: string; body: unknown }> = [] await mockOpenCodeServer(page, { @@ -52,10 +51,7 @@ test("creates a session in a new project, connects OpenCode Go, and selects its integrationMethods: { "opencode-go": [{ type: "api", label: "API key" }] }, onConnectKey: (input) => { connections.push(input) - if (input.integrationID === "opencode-go") pendingGo = true - }, - onInstanceDispose: () => { - if (pendingGo) connectedGo = true + if (input.integrationID === "opencode-go") connectedGo = true }, sessions: [], pageMessages: () => ({ items: [] }), @@ -63,6 +59,20 @@ test("creates a session in a new project, connects OpenCode Go, and selects its path ? [] : [{ name: "NewProject", path: "NewProject", absolute: directory, type: "directory", ignored: false }], findFiles: () => ["NewProject"], }) + await page.route( + (url) => url.pathname === "/api/integration", + (route) => + route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ + location: { directory }, + data: [ + { id: "opencode", name: "OpenCode", methods: [{ type: "key" }], connections: [] }, + { id: "opencode-go", name: "OpenCode Go", methods: [{ type: "key" }], connections: [] }, + ], + }), + }), + ) await page.addInitScript(() => { localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) localStorage.setItem("opencode.global.dat:server", JSON.stringify({ projects: { local: [] } })) @@ -85,7 +95,7 @@ test("creates a session in a new project, connects OpenCode Go, and selects its await page.locator('[data-input="provider-api-key"]').fill("mock-go-api-key") await page.locator('[data-action="provider-connect-submit"]').click() await expect(page.locator('[data-component="dialog-v2"]')).toHaveCount(0) - expect(connections).toEqual([{ integrationID: "opencode-go", body: { type: "api", key: "mock-go-api-key" } }]) + expect(connections).toEqual([{ integrationID: "opencode-go", body: { key: "mock-go-api-key" } }]) await expect(modelControl).toHaveAttribute("data-control-type", "popover") await modelControl.click() diff --git a/packages/app/e2e/utils/mock-server.ts b/packages/app/e2e/utils/mock-server.ts index fdb4df408880..910ad61dc1b3 100644 --- a/packages/app/e2e/utils/mock-server.ts +++ b/packages/app/e2e/utils/mock-server.ts @@ -199,6 +199,7 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { if (path === "/api/mcp") return json(route, { location: location(config), data: [] }) if (path === "/api/mcp/resource") return json(route, { location: location(config), data: { resources: [], templates: [] } }) + if (path === "/api/integration") return json(route, { location: location(config), data: [] }) const integration = path.match(/^\/api\/integration\/([^/]+)$/)?.[1] if (integration && route.request().method() === "GET") return json(route, { @@ -343,6 +344,13 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) { session.title = route.request().postDataJSON().title return route.fulfill({ status: 204, headers: { "access-control-allow-origin": "*" } }) } + const clearRevertMatch = path.match(/^\/api\/session\/([^/]+)\/revert\/clear$/) + if (clearRevertMatch && route.request().method() === "POST") { + const session = config.sessions.find((item) => item.id === clearRevertMatch[1]) + if (!session) return json(route, { error: "Session not found" }, undefined, 404) + delete session.revert + return route.fulfill({ status: 204, headers: { "access-control-allow-origin": "*" } }) + } if ( /^\/api\/session\/[^/]+\/(archive|rename|interrupt|revert\/clear|revert\/commit)$/.test(path) && route.request().method() === "POST" @@ -513,8 +521,11 @@ export function currentCatalog(config: Pick) { > }[] default?: { providerID?: string; modelID?: string } + connected?: string[] } - const providers = catalog.all ?? [] + const providers = (catalog.all ?? []).filter( + (provider) => !catalog.connected || catalog.connected.includes(provider.id ?? ""), + ) const models = providers.flatMap((provider) => Object.entries(provider.models ?? {}).map(([id, model]) => ({ id: model.id ?? id, diff --git a/packages/app/src/components/prompt-input-v2.tsx b/packages/app/src/components/prompt-input-v2.tsx index c356903e4682..1346a9938669 100644 --- a/packages/app/src/components/prompt-input-v2.tsx +++ b/packages/app/src/components/prompt-input-v2.tsx @@ -225,6 +225,7 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps): resetEditingQueueID: props.resetEditingQueueID, onQueue: props.onQueue, onAbort: props.onAbort, + onAbortComplete: props.onAbortComplete, revertMessageID: props.revertMessageID, onRevertSubmit: props.onRevertSubmit, onRevertSubmitComplete: props.onRevertSubmitComplete, diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 0be2cf66f271..2fa0c0daf336 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -1233,6 +1233,7 @@ export const PromptInput: Component = (props) => { resetEditingQueueID: props.resetEditingQueueID, onQueue: props.onQueue, onAbort: props.onAbort, + onAbortComplete: props.onAbortComplete, revertMessageID: props.revertMessageID, onRevertSubmit: props.onRevertSubmit, onRevertSubmitComplete: props.onRevertSubmitComplete, diff --git a/packages/app/src/components/prompt-input/contracts.ts b/packages/app/src/components/prompt-input/contracts.ts index f054972817f8..52e1c11ca151 100644 --- a/packages/app/src/components/prompt-input/contracts.ts +++ b/packages/app/src/components/prompt-input/contracts.ts @@ -57,6 +57,7 @@ export interface PromptInputProps { shouldQueue?: () => boolean onQueue?: (draft: FollowupDraft) => Promise | void onAbort?: () => Promise | void + onAbortComplete?: () => Promise | void revertMessageID?: () => string | undefined onRevertSubmit?: (messageID: string) => Promise | void onRevertSubmitComplete?: () => void diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts index 464bac62ae8c..394207415589 100644 --- a/packages/app/src/components/prompt-input/submit.test.ts +++ b/packages/app/src/components/prompt-input/submit.test.ts @@ -708,6 +708,9 @@ describe("prompt submit queue mode", () => { resetHistoryNavigation: () => undefined, setMode: () => undefined, setPopover: () => undefined, + onAbortComplete: () => { + abortOrder.push("complete") + }, onAbort: async () => { await gate abortOrder.push("pause") @@ -720,7 +723,7 @@ describe("prompt submit queue mode", () => { release() await pending - expect(abortOrder).toEqual(["pause", "interrupt"]) + expect(abortOrder).toEqual(["pause", "interrupt", "complete"]) expect(todoCleared).toEqual([]) expect(promptAsyncCalls).toHaveLength(0) }) diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index 6c05a149ad47..ec59c1d7d80e 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -92,6 +92,7 @@ type PromptSubmitInput = { resetEditingQueueID?: () => void onQueue?: (draft: FollowupDraft) => Promise | void onAbort?: () => Promise | void + onAbortComplete?: () => Promise | void revertMessageID?: Accessor onRevertSubmit?: (messageID: string) => Promise | void onRevertSubmitComplete?: () => void @@ -145,6 +146,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { } return serverSDK() .currentClient.sessions.interrupt({ sessionID }) + .then(() => input.onAbortComplete?.()) .catch((err) => { showToast({ title: language.t("common.requestFailed"), diff --git a/packages/app/src/context/global-sync/bootstrap.test.ts b/packages/app/src/context/global-sync/bootstrap.test.ts index 7bcbaf6de3b6..70e734c48a43 100644 --- a/packages/app/src/context/global-sync/bootstrap.test.ts +++ b/packages/app/src/context/global-sync/bootstrap.test.ts @@ -77,6 +77,13 @@ function directoryState() { } describe("bootstrapDirectory", () => { + test("loads supported path metadata for current session servers", async () => { + const paths = { state: "/state", config: "/config", worktree: "/repo", directory: "/repo", home: "/home/user" } + const client = { path: { get: async () => ({ data: paths }) } } as unknown as OpencodeClient + const query = new QueryClient() + expect(await query.fetchQuery(loadPathQuery(ServerScope.local, null, client, Promise.resolve("v2")))).toEqual(paths) + }) + test("uses legacy MCP endpoints while refreshing a v1 directory", async () => { const legacyConfigReads: string[] = [] const mcpReads: string[] = [] diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index 962c84db6e09..6ec6ed5e8cfd 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -304,9 +304,10 @@ export const loadPathQuery = ( queryOptions({ queryKey: [scope, directory, "path"], queryFn: async () => { - if ((await protocol) !== "v1") - return { state: "", config: "", worktree: "", directory: directory ?? "", home: "" } - return retry(() => sdk.path.get({ directory: directory ?? undefined }).then((result) => result.data!)) + // Session protocol detection does not describe support for the shared path endpoint. + const paths = retry(() => sdk.path.get({ directory: directory ?? undefined }).then((result) => result.data!)) + if ((await protocol) === "v1") return paths + return paths.catch(() => ({ state: "", config: "", worktree: "", directory: directory ?? "", home: "" })) }, }) diff --git a/packages/app/src/context/server-session.test.ts b/packages/app/src/context/server-session.test.ts index 2ebf5f88ef0b..e585b4121341 100644 --- a/packages/app/src/context/server-session.test.ts +++ b/packages/app/src/context/server-session.test.ts @@ -162,6 +162,33 @@ function setup(sessions: Record) { } describe("server session", () => { + test("loads current session todos through the supported endpoint and preserves live updates", async () => { + const todos = [{ content: "Persisted task", status: "in_progress", priority: "high" }] + const calls: string[] = [] + const client = { + session: { + todo: async (input: { sessionID: string }) => { + calls.push(input.sessionID) + return { data: todos } + }, + }, + } as unknown as OpencodeClient + const store = createServerSession(client, { protocol: Promise.resolve("v2") }) + store.remember(session("child")) + await store.todo("child") + expect(store.data.todo.child).toEqual(todos) + store.apply({ + type: "todo.updated", + properties: { sessionID: "child", todos: [{ ...todos[0], status: "completed" }] }, + }) + expect(store.data.todo.child?.[0]?.status).toBe("completed") + await store.todo("child") + expect(calls).toEqual(["child"]) + await store.todo("child", { force: true }) + expect(calls).toEqual(["child", "child"]) + expect(store.data.todo.child).toEqual(todos) + }) + test("projects V2 session events into current and legacy message state", () => { const ctx = setup({ child: session("child") }) ctx.store.remember(session("child")) diff --git a/packages/app/src/context/server-session.ts b/packages/app/src/context/server-session.ts index 16aadad10382..5cbc21924d6f 100644 --- a/packages/app/src/context/server-session.ts +++ b/packages/app/src/context/server-session.ts @@ -1380,10 +1380,6 @@ export function createServerSession( async todo(sessionID: string, request?: { force?: boolean }) { touch(sessionID) if (data.todo[sessionID] !== undefined && !request?.force) return - if ((await options?.protocol) === "v2") { - setData("todo", sessionID, []) - return - } return runInflight(inflightTodo, sessionID, () => { const active = generation(sessionID) return (options?.retry ?? retry)(() => client.session.todo({ sessionID })).then((result) => { diff --git a/packages/app/src/hooks/provider-catalog.test.ts b/packages/app/src/hooks/provider-catalog.test.ts index 99e3884ab883..dace26603411 100644 --- a/packages/app/src/hooks/provider-catalog.test.ts +++ b/packages/app/src/hooks/provider-catalog.test.ts @@ -1,6 +1,11 @@ import { expect, test } from "bun:test" import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" -import { resolveDefaultModel, selectProviderCatalog } from "./provider-catalog" +import { + loadProviderChoices, + mergeProviderChoices, + resolveDefaultModel, + selectProviderCatalog, +} from "./provider-catalog" const catalog = (id: string): NormalizedProviderListResponse => ({ all: new Map([[id, { id, name: id, source: "api", env: [], options: {}, models: {} }]]), @@ -8,6 +13,36 @@ const catalog = (id: string): NormalizedProviderListResponse => ({ default: { [id]: `${id}-model` }, }) +test("offers disconnected integrations without replacing configured provider models", () => { + const connected = catalog("opencode") + const all = mergeProviderChoices(connected.all, [ + { id: "opencode", name: "Generic" }, + { id: "opencode-go", name: "OpenCode Go" }, + ]) + expect(all.get("opencode")).toBe(connected.all.get("opencode")) + expect(all.get("opencode-go")).toMatchObject({ id: "opencode-go", name: "OpenCode Go", models: {} }) + expect(connected.all.has("opencode-go")).toBe(false) + expect(connected.connected).toEqual(["opencode"]) + expect(mergeProviderChoices(connected.all, [])).toBe(connected.all) +}) + +test("loads current integration choices and tolerates an unavailable catalogue", async () => { + const data = [{ id: "opencode-go", name: "OpenCode Go" }] + expect(await loadProviderChoices(Promise.resolve("v2"), async () => ({ data }))).toEqual(data) + expect(await loadProviderChoices(Promise.resolve("v2"), async () => ({}))).toEqual([]) + expect(await loadProviderChoices(Promise.resolve("v2"), async () => ({ data: [] }))).toEqual([]) + expect( + await loadProviderChoices(Promise.resolve("v2"), async () => { + throw new Error("offline") + }), + ).toEqual([]) + expect( + await loadProviderChoices(Promise.resolve("v1"), async () => { + throw new Error("legacy should not request integration API") + }), + ).toEqual([]) +}) + test("selects the ready catalog for an explicit directory", () => { const directory = catalog("directory") diff --git a/packages/app/src/hooks/provider-catalog.ts b/packages/app/src/hooks/provider-catalog.ts index d02b84c6334d..affc16f261cd 100644 --- a/packages/app/src/hooks/provider-catalog.ts +++ b/packages/app/src/hooks/provider-catalog.ts @@ -2,6 +2,31 @@ import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/con const emptyProviderCatalog: NormalizedProviderListResponse = { all: new Map(), connected: [], default: {} } +type ProviderChoice = { id: string; name: string } + +export async function loadProviderChoices( + protocol: Promise<"v1" | "v2">, + list: () => Promise<{ data?: readonly ProviderChoice[] }>, +) { + if ((await protocol) === "v1") return [] + return list() + .then((result) => (Array.isArray(result.data) ? result.data : [])) + .catch(() => []) +} + +export function mergeProviderChoices( + providers: NormalizedProviderListResponse["all"], + choices: readonly ProviderChoice[], +) { + if (!choices.length) return providers + const all = new Map(providers) + for (const choice of choices) { + if (all.has(choice.id)) continue + all.set(choice.id, { ...choice, source: "custom", env: [], options: {}, models: {} }) + } + return all +} + type DirectoryCatalog = { ready: boolean providers: NormalizedProviderListResponse diff --git a/packages/app/src/hooks/use-providers.ts b/packages/app/src/hooks/use-providers.ts index 7a7774b62e95..36c0407923d6 100644 --- a/packages/app/src/hooks/use-providers.ts +++ b/packages/app/src/hooks/use-providers.ts @@ -1,9 +1,10 @@ import { useServerSync } from "@/context/server-sync" +import { useServerSDK } from "@/context/server-sdk" import { decode64 } from "@/utils/base64" import { useParams } from "@solidjs/router" import { Iterable, pipe } from "effect" -import { createEffect, createMemo, type Accessor } from "solid-js" -import { selectProviderCatalog } from "./provider-catalog" +import { createEffect, createMemo, createResource, type Accessor } from "solid-js" +import { loadProviderChoices, mergeProviderChoices, selectProviderCatalog } from "./provider-catalog" export const popularProviders = [ "opencode", @@ -21,6 +22,16 @@ export function useProviders(directory: Accessor) { const serverSync = useServerSync() const params = useParams() const dir = () => (directory ? directory() : decode64(params.dir)) + const serverSDK = useServerSDK() + const [choices] = createResource( + () => ({ server: serverSDK(), directory: dir() }), + async (input) => ({ + ...input, + items: await loadProviderChoices(input.server.protocol, () => + input.server.api.integration.list(input.directory ? { location: { directory: input.directory } } : undefined), + ), + }), + ) const providers = () => { const value = dir() const projectStore = value ? serverSync().child(value)[0] : undefined @@ -38,13 +49,20 @@ export function useProviders(directory: Accessor) { }) } + const all = createMemo(() => { + const value = choices.latest + return mergeProviderChoices( + providers().all, + value?.server === serverSDK() && value.directory === dir() ? value.items : [], + ) + }) return { - all: () => providers().all, + all, default: () => providers().default, defaultModel: () => providers().defaultModel, popular: () => pipe( - providers().all, + all(), Iterable.map(([, p]) => p), Iterable.filter((p) => popularProviderSet.has(p.id)), (v) => Array.from(v), diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 383a3bb30bf0..3a4c83975387 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1,4 +1,5 @@ import type { FilePart, Project, Session, UserMessage, VcsFileDiff } from "@opencode-ai/sdk/v2" +import { DateTime } from "effect" import { getFilename } from "@opencode-ai/core/util/path" import { useDialog } from "@opencode-ai/ui/context/dialog" import { createQuery, skipToken, useMutation, useQueryClient } from "@tanstack/solid-query" @@ -528,11 +529,21 @@ export default function Page() { : sync().data.session_working(sessionID) createEffect( on( - () => [params.id, current.readiness(), current.busy()] as const, - ([id, readiness, active]) => { + () => [params.id, current.readiness(), current.busy(), current.retry()] as const, + ([id, readiness, active, retry]) => { if (!id || readiness !== "ready") return - const next = active ? ({ type: "busy" } as const) : ({ type: "idle" } as const) - if ((sync().data.session_status[id]?.type ?? "idle") === next.type) return + const next = + active && retry + ? { + type: "retry" as const, + attempt: retry.data.attempt, + message: retry.data.error.message, + next: DateTime.toEpochMillis(retry.data.timestamp), + } + : active + ? ({ type: "busy" } as const) + : ({ type: "idle" } as const) + if (next.type !== "retry" && (sync().data.session_status[id]?.type ?? "idle") === next.type) return sync().set("session_status", id, next) }, ), @@ -1173,6 +1184,10 @@ export default function Page() { setActiveMessage, focusInput, getMessageParts: timelineParts, + getUserMessages: userMessages, + getRevertMessageID: revertMessageID, + revert: (messageID) => (params.id ? revert({ sessionID: params.id, messageID }) : undefined), + restore: (messageID) => restore(messageID), review: reviewTab, fileBrowser: () => newSessionDesign() && isDesktop() && !!params.id, }) @@ -1869,6 +1884,7 @@ export default function Page() { const target = sync() const last = target.session.get(input.sessionID)?.revert const value = draft(input.messageID) + const owner = sessionOwnership.capture() await runPromptRollbackMutation({ capturePrompt: prompt.capture, optimistic: (prompt) => { @@ -1876,7 +1892,7 @@ export default function Page() { prompt.set(value) }, request: () => halt(input.sessionID).then(() => session.revert.stage({ ...input, inclusive: true })), - complete: () => setEditingRevert(input.messageID), + complete: () => owner.run(() => setEditingRevert(input.messageID)), rollback: () => roll(input.sessionID, last, target), fail, }) @@ -1894,6 +1910,7 @@ export default function Page() { if (index < 0) return const next = userMessages()[index + 1] const last = target.session.get(sessionID)?.revert + const owner = sessionOwnership.capture() await runPromptRollbackMutation({ capturePrompt: prompt.capture, @@ -1911,7 +1928,7 @@ export default function Page() { : halt(sessionID).then(() => session.revert.stage({ sessionID, messageID: next.id, inclusive: true }).then(() => undefined), ), - complete: () => setEditingRevert(next?.id), + complete: () => owner.run(() => setEditingRevert(next?.id)), rollback: () => roll(sessionID, last, target), fail, }) @@ -2249,6 +2266,7 @@ export default function Page() { onRevertSubmitComplete={() => setEditingRevert(undefined)} shouldQueue={queueEnabled} onQueue={queueFollowup} + onAbortComplete={current.refresh} onAbort={() => { const id = params.id if (!id) return Promise.resolve() @@ -2292,6 +2310,7 @@ export default function Page() { onRevertSubmitComplete: () => setEditingRevert(undefined), shouldQueue: queueEnabled, onQueue: queueFollowup, + onAbortComplete: current.refresh, onAbort: () => { const id = params.id if (!id) return Promise.resolve() diff --git a/packages/app/src/pages/session/current/model.test.ts b/packages/app/src/pages/session/current/model.test.ts index 9cafe812aa2f..0282f4fea412 100644 --- a/packages/app/src/pages/session/current/model.test.ts +++ b/packages/app/src/pages/session/current/model.test.ts @@ -120,6 +120,69 @@ describe("current session model", () => { ) }) + test("independent clients converge after replacement without dropping later admission", async () => { + const commit = Promise.withResolvers() + let committed = false + const port: CurrentSessionPort = { + ...makePort(), + sessions: { + ...makePort().sessions, + events: async function* (_input, options) { + await commit.promise + yield { + id: "evt_commit", + type: "session.next.revert.committed", + durable: { aggregateID: "ses_test", seq: 4, version: 1 }, + data: { sessionID: "ses_test", messageID: "msg_old", timestamp: 4 }, + } + await new Promise((resolve) => + options?.signal?.addEventListener("abort", () => resolve(), { once: true }), + ) + }, + }, + messages: { + list: async () => ({ + ...messages(committed ? [] : [{ id: "msg_old", text: "old", created: 1 }], undefined, committed ? 5 : 2), + pending: [ + { + id: committed ? "msg_new" : "msg_pending", + type: "user", + text: committed ? "replacement" : "old steer", + time: { created: committed ? 5 : 2 }, + }, + ], + }), + }, + } + await new Promise((resolve, reject) => + createRoot((dispose) => { + const clients = [0, 1].map(() => + createCurrentSessionModel({ sessionID: () => "ses_test", client: () => port, autoStart: false }), + ) + Promise.all(clients.map((client) => client.start())) + .then(async () => { + expect(clients.map((client) => client.messages().map((message) => String(message.id)))).toEqual([ + ["msg_old", "msg_pending"], + ["msg_old", "msg_pending"], + ]) + committed = true + commit.resolve() + await until(() => + clients.every((client) => client.messages().length === 1 && client.messages()[0]?.id === "msg_new"), + ) + expect(clients.map((client) => client.messages()[0])).toMatchObject([ + { id: "msg_new", text: "replacement" }, + { id: "msg_new", text: "replacement" }, + ]) + clients.forEach((client) => client.dispose()) + dispose() + resolve() + }) + .catch(reject) + }), + ) + }) + test("buffers SSE during hydration and refreshes server-authoritative queue state", async () => { let queueReads = 0 const event = prompted(4) diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx index 525dac250ae9..153a0c7b891c 100644 --- a/packages/app/src/pages/session/timeline/message-timeline.tsx +++ b/packages/app/src/pages/session/timeline/message-timeline.tsx @@ -330,7 +330,7 @@ export function MessageTimeline(props: { const id = sessionID() if (!id) return return parentMessages() - .flatMap((message) => getMsgParts(message.id)) + .flatMap((message) => sync().data.part[message.id] ?? emptyParts) .map((part) => taskDescription(part, id)) .findLast((value): value is string => !!value) }) diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index c1f20e9db8f7..01abf58e14f2 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -27,6 +27,10 @@ export type SessionCommandContext = { setActiveMessage: (message: UserMessage | undefined) => void focusInput: () => void getMessageParts?: (messageID: string) => Part[] + getUserMessages?: () => UserMessage[] + getRevertMessageID?: () => string | undefined + revert?: (messageID: string) => Promise | undefined + restore?: (messageID: string) => Promise | undefined review?: () => boolean fileBrowser?: () => boolean } @@ -99,9 +103,11 @@ export const useSessionCommands = (actions: SessionCommandContext) => { if (!id) return [] return sync().data.message[id] ?? [] } - const userMessages = () => messages().filter((m) => m.role === "user") as UserMessage[] + const userMessages = () => + actions.getUserMessages?.() ?? (messages().filter((m) => m.role === "user") as UserMessage[]) + const revertMessageID = () => (actions.getRevertMessageID ? actions.getRevertMessageID() : info()?.revert?.messageID) const visibleUserMessages = () => { - const revert = info()?.revert?.messageID + const revert = revertMessageID() if (!revert) return userMessages() const boundary = userMessages().findIndex((message) => message.id === revert) return boundary < 0 ? userMessages() : userMessages().slice(0, boundary) @@ -339,17 +345,18 @@ export const useSessionCommands = (actions: SessionCommandContext) => { const session = sdk().api.session const directory = sdk().directory const promptSession = prompt.capture() - const revert = info()?.revert?.messageID + const revert = revertMessageID() const messages = userMessages() const boundary = revert ? messages.findIndex((message) => message.id === revert) : messages.length if (boundary < 0) return const message = messages[boundary - 1] if (!message) return + if (actions.revert) return actions.revert(message.id) const projectedParts = actions.getMessageParts?.(message.id) const parts = projectedParts?.length ? projectedParts : sync().data.part[message.id] if (sync().data.session_working(sessionID)) { - await session.interrupt({ sessionID }).catch(() => {}) + await session.interrupt({ sessionID }) } await runCommand({ @@ -371,10 +378,11 @@ export const useSessionCommands = (actions: SessionCommandContext) => { const messages = userMessages() const promptSession = prompt.capture() - const revertMessageID = info()?.revert?.messageID - if (!revertMessageID) return + const reverted = revertMessageID() + if (!reverted) return + if (actions.restore) return actions.restore(reverted) - const boundary = messages.findIndex((message) => message.id === revertMessageID) + const boundary = messages.findIndex((message) => message.id === reverted) if (boundary < 0) return const next = messages[boundary + 1] if (!next) { @@ -474,7 +482,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { title: language.t("command.session.redo"), description: language.t("command.session.redo.description"), slash: "redo", - disabled: !params.id || !info()?.revert?.messageID, + disabled: !params.id || !revertMessageID(), onSelect: redo, }), sessionCommand({ diff --git a/packages/app/src/utils/session-message.test.ts b/packages/app/src/utils/session-message.test.ts index 2b0f93ff41ae..1e4aea53e18b 100644 --- a/packages/app/src/utils/session-message.test.ts +++ b/packages/app/src/utils/session-message.test.ts @@ -7,6 +7,40 @@ import { normalizeCurrentSessionMessages, normalizeSessionMessages } from "./ses const decodeCurrentMessage = Schema.decodeUnknownSync(SessionMessage.Message) describe("normalizeSessionMessages", () => { + test("maps current task child identities for navigation without replacing legacy metadata", () => { + for (const structured of [{ sessionID: "child" }, { sessionID: "child", sessionId: "legacy-child" }]) { + const result = normalizeCurrentSessionMessages("parent", [ + decodeCurrentMessage({ + id: "msg_user", + type: "user", + text: "Delegate", + files: [], + agents: [], + time: { created: 1 }, + }), + decodeCurrentMessage({ + id: "msg_assistant", + type: "assistant", + agent: "build", + model: { id: "model", providerID: "provider" }, + time: { created: 2, completed: 3 }, + content: [ + { + id: "task", + type: "tool", + name: "task", + time: { created: 2, completed: 3 }, + state: { status: "completed", input: { description: "Child task" }, structured, content: [] }, + }, + ], + }), + ]) + expect(result.parts.get("msg_assistant")).toMatchObject([ + { type: "tool", state: { metadata: { ...structured, sessionId: structured.sessionId ?? "child" } } }, + ]) + } + }) + test("adapts current messages for the compatibility timeline", () => { const result = normalizeCurrentSessionMessages("ses_1", [ decodeCurrentMessage({ @@ -62,6 +96,54 @@ describe("normalizeSessionMessages", () => { ]) }) + test("preserves current user attachments and payload comments in the compatibility projection", () => { + const text = "Use @explore with @src/a.ts" + const parts = [ + { + type: "text", + text: "Comment context", + synthetic: true, + metadata: { + opencodeComment: { path: "src/a.ts", comment: "Keep stable", selection: { startLine: 4, endLine: 8 } }, + }, + }, + { type: "text", text }, + { type: "file", mime: "image/png", filename: "pixel.png", url: "data:image/png;base64,eA==" }, + { + type: "file", + mime: "text/plain", + filename: "a.ts", + url: "src/a.ts", + source: { type: "file", path: "src/a.ts", text: { value: "@src/a.ts", start: 18, end: 27 } }, + }, + { type: "agent", name: "explore", source: { value: "@explore", start: 4, end: 12 } }, + ] + const message = { + id: "msg_rich", + type: "user", + text, + files: [{ uri: "data:image/png;base64,eA==", mime: "image/png", name: "pixel.png" }], + agents: [{ name: "explore", source: { text: "@explore", start: 4, end: 12 } }], + time: { created: 1 }, + } + const projected = normalizeCurrentSessionMessages("ses_1", [decodeCurrentMessage(message)]) + expect(projected.parts.get("msg_rich")).toMatchObject([ + { type: "text", text }, + { type: "file", url: "data:image/png;base64,eA==", filename: "pixel.png" }, + { type: "agent", source: { value: "@explore", start: 4, end: 12 } }, + ]) + const result = normalizeCurrentSessionMessages("ses_1", [ + decodeCurrentMessage({ + ...message, + payload: { version: 1, agent: "build", model: { providerID: "provider", modelID: "model" }, parts }, + }), + ]) + expect(result.parts.get("msg_rich")).toMatchObject(parts) + expect( + result.parts.get("msg_rich")?.every((part) => part.sessionID === "ses_1" && part.messageID === "msg_rich"), + ).toBe(true) + }) + test("renders current synthetic text, structured tool metadata, and snapshot diffs", () => { const result = normalizeCurrentSessionMessages("ses_1", [ decodeCurrentMessage({ diff --git a/packages/app/src/utils/session-message.ts b/packages/app/src/utils/session-message.ts index f03a60bf1c1d..c3b1cd8905af 100644 --- a/packages/app/src/utils/session-message.ts +++ b/packages/app/src/utils/session-message.ts @@ -44,6 +44,18 @@ export function normalizeCurrentSessionMessages(sessionID: string, source: reado } function toLegacyMessage(message: CurrentEncodedMessage): SessionMessageInfo { + if (message.type === "user") { + return { + ...message, + files: message.files?.map((file) => ({ + ...file, + data: "", + source: { type: "uri" as const, uri: file.uri }, + mention: file.source, + })), + agents: message.agents?.map((agent) => ({ ...agent, mention: agent.source })), + } as SessionMessageInfo + } if (message.type === "shell") { return { ...message, @@ -111,6 +123,8 @@ function normalizeToolInput(name: string, input: Record) { } function normalizeToolMetadata(name: string, metadata: Record) { + if (name === "task" && typeof metadata.sessionID === "string" && metadata.sessionId === undefined) + return { ...metadata, sessionId: metadata.sessionID } if (name !== "edit" || !Array.isArray(metadata.files)) return metadata const file = metadata.files.find(record) if (!file || typeof file.file !== "string") return metadata @@ -284,7 +298,19 @@ function userMessage( } } -function userParts(sessionID: string, message: SessionMessageUser): Part[] { +function userParts( + sessionID: string, + message: SessionMessageUser & { payload?: Extract["payload"] }, +): Part[] { + if (message.payload) { + const ordinals = { text: 0, file: 0, agent: 0, subtask: 0 } + return message.payload.parts.map((part) => ({ + ...part, + id: `${message.id}:${part.type}:${ordinals[part.type]++}`, + sessionID, + messageID: message.id, + })) + } return [ textPart(sessionID, message.id, 0, message.text), ...(message.files ?? []).map( @@ -391,6 +417,12 @@ function textPart(sessionID: string, messageID: string, ordinal: number, text: s function toolPart(sessionID: string, messageID: string, tool: SessionMessageAssistantTool): ToolPart { const start = tool.time.ran ?? tool.time.created + const metadata = + "structured" in tool.state && record(tool.state.structured) + ? tool.state.structured + : "metadata" in tool.state + ? (tool.state.metadata ?? {}) + : {} const state = (() => { if (tool.state.status === "streaming") { const value = Option.getOrUndefined(decodeToolInput(tool.state.input)) @@ -401,8 +433,7 @@ function toolPart(sessionID: string, messageID: string, tool: SessionMessageAssi return { status: "running" as const, input: normalizeToolInput(tool.name, tool.state.input), - // metadata: normalizeToolMetadata(tool.name, tool.state.structured), - metadata: normalizeToolMetadata(tool.name, tool.state.metadata ?? {}), + metadata: normalizeToolMetadata(tool.name, metadata), time: { start }, } } @@ -411,8 +442,7 @@ function toolPart(sessionID: string, messageID: string, tool: SessionMessageAssi status: "error" as const, input: normalizeToolInput(tool.name, tool.state.input), error: tool.state.error.message, - // metadata: normalizeToolMetadata(tool.name, tool.state.structured), - metadata: normalizeToolMetadata(tool.name, tool.state.metadata ?? {}), + metadata: normalizeToolMetadata(tool.name, metadata), time: { start, end: tool.time.completed ?? start }, } } @@ -436,8 +466,7 @@ function toolPart(sessionID: string, messageID: string, tool: SessionMessageAssi input: normalizeToolInput(tool.name, tool.state.input), output: tool.state.content.flatMap((item) => (item.type === "text" ? [item.text] : [])).join("\n"), title: tool.name, - // metadata: normalizeToolMetadata(tool.name, tool.state.structured), - metadata: normalizeToolMetadata(tool.name, tool.state.metadata ?? {}), + metadata: normalizeToolMetadata(tool.name, metadata), time: { start, end: tool.time.completed ?? start }, attachments: attachments.length ? attachments : undefined, } diff --git a/packages/core/test/session-replacement.test.ts b/packages/core/test/session-replacement.test.ts index 7077eaf59620..51b3445396bf 100644 --- a/packages/core/test/session-replacement.test.ts +++ b/packages/core/test/session-replacement.test.ts @@ -40,6 +40,18 @@ for (const promoted of [true, false]) { version: "test", }) .run() + const otherSessionID = SessionV2.ID.make("ses_unrelated") + yield* database.db + .insert(SessionTable) + .values({ + id: otherSessionID, + project_id: Project.ID.global, + slug: "unrelated", + directory: "/project", + title: "unrelated", + version: "test", + }) + .run() yield* database.db .insert(SessionInputTable) .values([ @@ -65,6 +77,20 @@ for (const promoted of [true, false]) { delivery: "queue", admitted_seq: 4, }, + { + id: SessionMessage.ID.make("msg_at_cutoff"), + session_id: sessionID, + prompt: { text: "last pre-stage steer" }, + delivery: "steer", + admitted_seq: 5, + }, + { + id: SessionMessage.ID.make("msg_unrelated"), + session_id: otherSessionID, + prompt: { text: "other session" }, + delivery: "steer", + admitted_seq: 3, + }, { id: SessionMessage.ID.make("msg_replacement"), session_id: sessionID, @@ -93,9 +119,9 @@ for (const promoted of [true, false]) { ), }) .run() - const revert = { messageID: id, inclusive: true, inputThroughSeq: 4 } + const revert = { messageID: id, inclusive: true, inputThroughSeq: 5 } yield* events.publish(SessionEvent.RevertEvent.Staged, { sessionID, timestamp: DateTime.makeUnsafe(1), revert }) - expect((yield* database.db.select().from(SessionInputTable).all()).length).toBe(4) + expect((yield* database.db.select().from(SessionInputTable).all()).length).toBe(6) yield* events.publish(SessionEvent.RevertEvent.Committed, { sessionID, messageID: id, @@ -103,7 +129,7 @@ for (const promoted of [true, false]) { }) expect(yield* database.db.select().from(SessionMessageTable).all()).toEqual([]) expect((yield* database.db.select().from(SessionInputTable).all()).map((row) => String(row.id)).sort()).toEqual( - ["msg_queue", "msg_replacement"], + ["msg_queue", "msg_replacement", "msg_unrelated"], ) }), ) From 84412e0a2793a5d365db87d786d0c1a589c55091 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 09:12:16 -0300 Subject: [PATCH 022/129] test(ci): stabilize fork runners and portable subprocess fixtures --- .github/actions/setup-bun/action.yml | 6 +++++- .github/workflows/test.yml | 14 +++++++------ .github/workflows/typecheck.yml | 2 +- packages/core/test/command.test.ts | 16 +++++++-------- packages/core/test/fixture/mcp-stdio.ts | 3 ++- packages/core/test/session-prompt.test.ts | 25 +++++++++++++++-------- packages/opencode/package.json | 2 +- turbo.json | 3 ++- 8 files changed, 44 insertions(+), 27 deletions(-) diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index 9d29724d86a4..c1ec3388f481 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -1,6 +1,10 @@ name: "Setup Bun" description: "Setup Bun with caching and install dependencies" inputs: + node-version: + description: "Node.js version used by dependency install scripts" + required: false + default: "24" install-flags: description: "Additional flags to pass to 'bun install'" required: false @@ -13,7 +17,7 @@ runs: - name: Setup Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - node-version: "24" + node-version: ${{ inputs.node-version }} - name: Get baseline download URL id: bun-url diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7542a65f23cc..f723fddc9464 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,6 +55,10 @@ jobs: git config --global user.email "bot@opencode.ai" git config --global user.name "opencode" + - name: Install ripgrep + if: runner.os == 'Windows' + run: choco install ripgrep --version 15.1.0 --yes --no-progress + - name: Cache Turbo uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: @@ -66,7 +70,7 @@ jobs: - name: Run unit tests timeout-minutes: 20 - run: GITHUB_ACTIONS=false bun turbo test + run: GITHUB_ACTIONS=false bun turbo test --concurrency=${{ github.repository == 'anomalyco/opencode' && '10' || '2' }} env: OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }} @@ -93,6 +97,7 @@ jobs: runs-on: ${{ github.repository == 'anomalyco/opencode' && matrix.settings.host || (matrix.settings.name == 'windows' && 'windows-latest' || 'ubuntu-latest') }} env: PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers + PLAYWRIGHT_WORKERS: ${{ github.repository == 'anomalyco/opencode' && '5' || '2' }} defaults: run: shell: bash @@ -102,15 +107,12 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} - - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - name: Setup Bun + uses: ./.github/actions/setup-bun with: # Playwright 1.59 hangs while extracting Chromium with Node 24.16. node-version: "24.15" - - name: Setup Bun - uses: ./.github/actions/setup-bun - - name: Read Playwright version id: playwright-version run: | diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 1f8e95807dd5..ef65449f8989 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -18,7 +18,7 @@ jobs: uses: ./.github/actions/setup-bun - name: Run typecheck - run: bun typecheck + run: bun typecheck --concurrency=${{ github.repository == 'anomalyco/opencode' && '10' || '2' }} - name: Typecheck browser regression fixtures working-directory: packages/app diff --git a/packages/core/test/command.test.ts b/packages/core/test/command.test.ts index 504b29ede961..ce7ac26db306 100644 --- a/packages/core/test/command.test.ts +++ b/packages/core/test/command.test.ts @@ -7,6 +7,7 @@ import { Location } from "@opencode-ai/core/location" import { ModelV2 } from "@opencode-ai/core/model" import { ProviderV2 } from "@opencode-ai/core/provider" import { AbsolutePath } from "@opencode-ai/core/schema" +import { Shell } from "@opencode-ai/core/shell" import { location } from "./fixture/location" import { testEffect } from "./lib/effect" @@ -14,13 +15,7 @@ const directory = AbsolutePath.make(process.cwd()) const it = testEffect( AppNodeBuilder.build(CommandV2.node, [ [Config.node, Layer.succeed(Config.Service, Config.Service.of({ entries: () => Effect.succeed([]) }))], - [ - Location.node, - Layer.succeed( - Location.Service, - Location.Service.of(location(Location.Ref.make({ directory }))), - ), - ], + [Location.node, Layer.succeed(Location.Service, Location.Service.of(location(Location.Ref.make({ directory }))))], ]), ) @@ -80,7 +75,12 @@ describe("CommandV2", () => { const command = yield* CommandV2.Service yield* command.transform((editor) => { editor.update("review", (item) => { - item.template = "First=$1 Rest=$2 Shell=!`printf ' value '; printf 'ignored' >&2`" + item.template = + "First=$1 Rest=$2 Shell=!`" + + (Shell.ps(Shell.preferred() ?? "") + ? "[Console]::Out.Write(' value '); [Console]::Error.Write('ignored')" + : "printf ' value '; printf 'ignored' >&2") + + "`" item.source = "command" }) }) diff --git a/packages/core/test/fixture/mcp-stdio.ts b/packages/core/test/fixture/mcp-stdio.ts index 1cd199e30ab7..f21461d676fa 100644 --- a/packages/core/test/fixture/mcp-stdio.ts +++ b/packages/core/test/fixture/mcp-stdio.ts @@ -1,6 +1,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js" import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" import { GetPromptRequestSchema, ListPromptsRequestSchema } from "@modelcontextprotocol/sdk/types.js" +import path from "node:path" const server = new Server({ name: "core-mcp-stdio-test", version: "1.0.0" }, { capabilities: { prompts: {} } }) @@ -14,7 +15,7 @@ server.setRequestHandler(GetPromptRequestSchema, (request) => messages: [ { role: "user", - content: { type: "text", text: `${process.cwd()}/${request.params.arguments?.suffix ?? ""}` }, + content: { type: "text", text: path.join(process.cwd(), request.params.arguments?.suffix ?? "") }, }, ], }), diff --git a/packages/core/test/session-prompt.test.ts b/packages/core/test/session-prompt.test.ts index b169bbd5023b..976dc609f8e9 100644 --- a/packages/core/test/session-prompt.test.ts +++ b/packages/core/test/session-prompt.test.ts @@ -24,7 +24,10 @@ import { LocationServiceMap } from "@opencode-ai/core/location-service-map" import { ModelV2 } from "@opencode-ai/core/model" import { ProviderV2 } from "@opencode-ai/core/provider" import { Config } from "@opencode-ai/core/config" +import { Shell } from "@opencode-ai/core/shell" import { SessionInputPayload } from "@opencode-ai/schema/session-input-payload" +import os from "node:os" +import path from "node:path" import { testEffect } from "./lib/effect" const executionCalls: SessionV2.ID[] = [] @@ -186,12 +189,15 @@ describe("SessionV2.prompt", () => { .pipe(Effect.orDie) const session = yield* SessionV2.Service - yield* session.shell({ sessionID, command: "printf current-shell-output" }) + const command = Shell.ps(Shell.preferred() ?? "") + ? "[Console]::Out.Write('current-shell-output')" + : "printf current-shell-output" + yield* session.shell({ sessionID, command }) expect(yield* session.messages({ sessionID, order: "asc" })).toMatchObject([ { type: "shell", - command: "printf current-shell-output", + command, output: "current-shell-output", }, ]) @@ -738,10 +744,16 @@ describe("SessionV2.command", () => { const command = yield* CommandV2.Service.pipe(Effect.provide(locations.get(location))) const agents = yield* AgentV2.Service.pipe(Effect.provide(locations.get(location))) yield* agents.transform((draft) => draft.update(AgentV2.ID.make("build"), () => undefined)) - const sideEffect = `/tmp/opencode-command-retry-${crypto.randomUUID()}` + const sideEffect = path.join(os.tmpdir(), `opencode-command-retry-${crypto.randomUUID()}`) + yield* Effect.addFinalizer(() => Effect.promise(() => Bun.file(sideEffect).delete()).pipe(Effect.ignore)) yield* command.transform((draft) => draft.update("retry-safe", (item) => { - item.template = `!` + "`" + `printf x >> '${sideEffect}'; printf expanded` + "`" + item.template = + "!`" + + (Shell.ps(Shell.preferred() ?? "") + ? `[IO.File]::AppendAllText('${sideEffect.replaceAll("'", "''")}', 'x'); [Console]::Out.Write('expanded')` + : `printf x >> '${sideEffect.replaceAll("'", "'\\''")}'; printf expanded`) + + "`" }), ) const id = SessionMessage.ID.create() @@ -789,7 +801,6 @@ describe("SessionV2.command", () => { expect(conflict._tag).toBe("Session.PromptConflictError") expect(yield* Effect.promise(() => Bun.file(sideEffect).text())).toBe("x") expect(yield* eventCount("session.next.command.executed.1")).toBe(1) - yield* Effect.promise(() => Bun.file(sideEffect).delete()) }), ) @@ -1001,9 +1012,7 @@ describe("SessionV2.queue", () => { id: second.id, discardedSeq: expect.any(Number), }) - expect( - (yield* session.history({ sessionID, limit: 20 })).events.map((event) => event.type), - ).toEqual([ + expect((yield* session.history({ sessionID, limit: 20 })).events.map((event) => event.type)).toEqual([ "session.next.prompt.admitted", "session.next.prompt.admitted", "session.next.prompt.revised", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 401fb356dd2e..cac8579d7ab0 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -7,7 +7,7 @@ "private": true, "scripts": { "typecheck": "tsgo --noEmit", - "test": "bun test --timeout 30000 --only-failures", + "test": "bun test --timeout 30000 --max-concurrency 2 --only-failures", "test:httpapi": "bun run script/httpapi-exercise.ts --mode coverage --fail-on-missing --fail-on-skip && bun run script/httpapi-exercise.ts --mode auth --fail-on-missing --fail-on-skip && bun run script/httpapi-exercise.ts --mode effect --fail-on-missing --fail-on-skip", "bench:test": "bun run script/bench-test-suite.ts", "profile:test": "bun run script/profile-test-files.ts", diff --git a/turbo.json b/turbo.json index daf89195b715..03ee54cf8ac8 100644 --- a/turbo.json +++ b/turbo.json @@ -15,7 +15,8 @@ }, "@opencode-ai/core#test": { "dependsOn": ["^build"], - "outputs": [] + "outputs": [], + "passThroughEnv": ["OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER", "OPENCODE_TEST_HOME", "XDG_*"] }, "@opencode-ai/function#test": { "outputs": [] From a57006841e31bca782ed2e8f0c5b5630c5cd6212 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 09:12:16 -0300 Subject: [PATCH 023/129] docs(app): record accepted follow-ups and throttled benchmark context --- docs/fork/web-audit.md | 197 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 184 insertions(+), 13 deletions(-) diff --git a/docs/fork/web-audit.md b/docs/fork/web-audit.md index 980a67c5ad84..9c4e4a9ea2fc 100644 --- a/docs/fork/web-audit.md +++ b/docs/fork/web-audit.md @@ -4,10 +4,20 @@ Updated: 2026-09-05. ## Release decision -**Not ready to merge.** The integration branches contain upstream through -`70b4ca8c181e4c1ac6d8993b86249d824487ec65` and verified fixes, but the full browser -suite is not green. Keep production PR #7 and development PR #8 as drafts. -No finite audit establishes that the entire fork is bug-free. +**Merge authorized with known follow-ups.** On 2026-09-05 the repository owner +explicitly requested committing and pushing every current improvement and merging +production PR #7 and development PR #8, with remaining issues handled in follow-up +PRs. This supersedes the earlier draft/release hold. It does not mean the full +browser suite or hosted CI is green. The checkpoint below records the evidence +and unresolved work without weakening or skipping failing tests. + +The owner also confirmed that the CPU was throttled during the slower benchmark. +Those timings are not comparable to the earlier unthrottled baseline and are not +established as a code-induced performance regression. + +Integrated upstream remains `70b4ca8c181e4c1ac6d8993b86249d824487ec65`; +newer upstream work is explicitly tracked below. No finite audit establishes that +the entire fork is bug-free. The deployed service on ports 4096/14096 and its binary were not changed. Tests used separate worktrees, loopback servers, synthetic sessions and isolated XDG @@ -17,13 +27,13 @@ test asserting Basic-auth headers with dummy credentials. ## Comparison boundary -| Ref | Commit | -| ----------------------------------------------------- | ------------------------------------------ | -| Fork production baseline | `c94eb6133eca258cb06cc30d159aae7a76519d1b` | -| Fork development baseline | `722450048f` | -| Initial upstream production | `20a7743876` | -| Initial upstream development | `79903a4cf7` | -| Latest upstream fetched and integrated, both branches | `70b4ca8c181e4c1ac6d8993b86249d824487ec65` | +| Ref | Commit | +| ------------------------------------ | ------------------------------------------ | +| Fork production baseline | `c94eb6133eca258cb06cc30d159aae7a76519d1b` | +| Fork development baseline | `722450048f` | +| Initial upstream production | `20a7743876` | +| Initial upstream development | `79903a4cf7` | +| Upstream integrated in both branches | `70b4ca8c181e4c1ac6d8993b86249d824487ec65` | The initial production delta was 510 files, 38,512 additions / 7,297 deletions. The latest upstream merge did not change app/session-ui runtime source relative @@ -210,7 +220,7 @@ tooltip token count, and detail-screen total/percentage against explicit values. harmless. Repeat isolated performance measurements before release. No wrong-destination or review-host replacement samples were observed. -## Remaining full-suite failure inventory +## Historical full-suite failure inventory (before checkpoint) These are failing test cases, not 55 confirmed product bugs. Several fixtures still send mutation arrays as one SSE event or assert obsolete part IDs. Repair invalid @@ -234,7 +244,7 @@ that remain against valid data. | New-project model-selection story | 2 | | Review/terminal stacking (Firefox) | 1 | -## Ordered remaining work +## Historical work plan (superseded by checkpoint backlog) 1. [x] Correct remote settings fixtures and verify cross-server auto-accept, including unfocused parent/child sessions. This was fixture protocol drift, @@ -264,3 +274,164 @@ Keep fork behavior behind focused modules and optional protocol fields. Prefer small call-site adapters over editing vendored client archives or broadly rewriting upstream UI. Preserve upstream defaults. Every future sync must validate these boundaries, not merely resolve textual merge conflicts. + +## Saved-work checkpoint — 2026-09-05 + +This is a reviewed **checkpoint with known unresolved issues**. The owner has +authorized merging it and deferring the remaining work to follow-up PRs. No runtime features were removed to make the upstream diff smaller. +The changes use existing adapters and optional call-site callbacks; they do not +replace the session architecture. The user's live service and installed binary +remain untouched. + +### Additional underlying fixes saved + +- **Stop before a provider turn:** interruption could succeed before any assistant + step existed, leaving no step-ended event to clear the composer's busy state. + After a successful interrupt, both composers now refresh the current session. + Queue draining is paused first. Admitted steering is neither deleted nor + resubmitted. The new browser case failed before this change and passes after it, + including reload. The submit test asserts pause → interrupt → refresh ordering. +- **Native user projection:** compatibility conversion now preserves native file + URIs, agent mentions and original typed payload parts, including synthetic + comment context. Native rich prompts no longer depend on lossy legacy fields. +- **Child-task navigation:** native task metadata uses `sessionID`, while legacy + cards read `sessionId`. The adapter supplies the alias without replacing an + existing legacy value, and reads native structured metadata. Child headings + resolve descriptions from the parent's cached parts, not the child-only current + message accessor. +- **Retry display:** the current-to-compatibility status bridge now includes retry + metadata and updates attempts even when the status tag has not changed. Native + retry → recovery → idle browser coverage asserts both attempts. +- **Supported shared endpoints:** V2 session detection no longer forces empty + todos or path metadata. The existing todo endpoint remains authoritative for + persisted tasks; live updates and forced refresh retain their existing behavior. + Path lookup retains an empty fallback only when unavailable on a V2 server. +- **Keyboard rollback integration:** Undo/Redo now receive current user messages, + including pending steering, and delegate to the same rollback/restore mutations + as the timeline. Completion is guarded against session navigation. Undo reaches + the correct pending draft in the new regression; the full Redo interaction is + still blocked by the failure below and is not claimed fixed end-to-end. +- **Provider discovery:** onboarding choices come from the integration catalogue, + separately from the connected-model catalogue. Missing/offline responses are + tolerated; models and connected/default selections are not invented. This fixes + the discovery boundary but exposes a remaining connection-dialog hydration + defect; the whole onboarding flow is still incomplete. +- **Replacement consistency:** two independent current-session clients converge + after a committed replacement. Persistence tests cover a steer exactly at the + admission cutoff, a later replacement, explicit queues, and an unrelated session. + +### Test-fixture and CI improvements saved + +Native timeline fixtures now send individual events with the correct aggregate +ID, durable sequence, normalized text/reasoning IDs and current endpoint envelopes. +Reconnect tests assert the durable `after` cursor. Pagination distinguishes the +current 100-message page from compatibility hydration. Browser assertions still +check behavior, ordering, geometry, caret restoration and error absence; failing +cases were not skipped or given relaxed assertions. Expanded e2e typechecking is +explicitly enumerated, not comprehensive coverage of every Playwright file. + +The shared Bun setup action now honors its caller's Node version, so the existing +Playwright Node 24.15 pin is no longer silently overwritten. Fork CI uses lower +concurrency; upstream workflow concurrency is retained. CLI tests cap concurrent +subprocess tests at two without increasing their timeouts. Windows fixtures use +portable paths and shell syntax, ripgrep is preinstalled, and Turbo forwards the +existing isolated-home/file-watcher environment into core tests. + +The earlier GitHub failures were **not established as quota exhaustion**: + +- Typecheck jobs exited 137 or were cancelled without TypeScript diagnostics. + Resource pressure is plausible, but OOM was not proven. +- Linux subprocess timeout failures reproduced locally with cold concurrent + compilation. Pinned Bun 1.3.14 with concurrency two passed all 14 CLI cases with + the same deadlines (49 assertions). +- Windows showed real portability/download issues as well as a distinct patched + Bun dependency installation `ENOTEMPTY` failure. The portability changes are + tested on Linux; a clean Windows pass is still required. A stock Windows smoke + VM was used for investigation, but its attempted install is not a passing result. + +### Checkpoint verification + +Commands below run from the named package directory, never root `bun test`. + +| Validation | Observed result | +| ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | +| App `bun run test` | 800 unit + 51 browser-environment unit tests passed; zero failures | +| App `bun typecheck` and `bun run typecheck:e2e` | Both passed | +| Core `bun test test/command.test.ts test/session-prompt.test.ts test/session-replacement.test.ts` | 38 passed, 100 assertions | +| Timeline fixture unit test | 4 passed, 8 assertions | +| Workflow `actionlint` (test and typecheck) | Passed | +| Full pinned-Bun Linux Turbo unit run before the final focused additions | 10/10 tasks passed, uncached, 14m43s; core 1,142 tests; opencode 3,627 pass / 22 skip / 1 todo | +| Seven timeline projection/lifecycle/geometry specs, Chromium + Firefox | 43 passed; 3 existing Firefox CDP skips | +| Stop, pending reload, rollback and native transport focused run | 24 passed, 2 failed; only command Undo/Redo failed | +| Recorded, rolled-back and live context usage | 6 passed across Chromium + Firefox | +| Fresh request/todo/child/review/onboarding/smoke run | 18 passed, 8 failed across Chromium + Firefox | + +These are separate runs with some overlapping cases, not an aggregate full-suite +pass. The fresh 26-case run confirms request-dock caret, todo lifecycle, child +navigation, prepend anchoring and cold-tab paint behavior. Its eight failures are +listed below. A final full browser run on both exact branch heads remains follow-up work. + +### Actionable backlog for follow-up PRs + +| ID / priority | Evidence and likely boundary | Required next step / exit criterion | +| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| WEB-01 / P1 | New-project OpenCode Go discovery succeeds, but selecting it throws `Cannot read properties of undefined (reading 'name')` in `ProviderConnection` / `MethodSelection`, before the API-key field appears (both browsers). `provider()` assumes that a new hook instance already has either integration choices or a connected provider. | Make connection rendering safe during catalogue hydration without inventing connected models. Add a delayed-catalogue regression and complete key submission, refresh and model selection in both browsers. | +| WEB-02 / P1 | Command Undo selects/stages the pending message correctly; the subsequent Ctrl+P for Redo never produces a dialog textbox in either browser. No error page in the clean repro. | Separate command-dialog/focus teardown from a runtime hotkey defect; retain real keyboard activation. Require stage, clear, empty draft and restored single message assertions to pass. | +| WEB-03 / P2 | Review/terminal stacking times out waiting for a tree to have positive height in both browsers in the fresh run. Native lifecycle events are now used. | Inspect layout readiness and fixture review mode before changing runtime. Verify tree/terminal geometry, scrolling, detail refresh and remount invariants, without fixed sleep inflation. | +| WEB-04 / P2 | Cached-tab smoke reports one removed first-paint plain text `` in both browsers; latest-message and bottom placement assertions already pass. Probe currently tracks every descendant, including `HighlightedText` leaves rebuilt during hydration. | Establish whether a user-visible row/part remount occurs. Preserve semantic row identity and first-frame assertions; distinguish incidental leaf updates from structural replacement with a dedicated repro rather than blindly relaxing zero-removal. | +| WEB-05 / P2 | Full-history smoke reaches its final error audit, then fails: Chromium logs a current-context transport error; Firefox logs a global event-stream failure. No forbidden text or error toast was observed. | Determine mock coverage/reconnect or cancellation logging versus a real transport failure; preserve the no-console-errors contract and full ordering checks. | +| CI-01 / P1 | Windows patched dependency install failed with `ENOTEMPTY`; clean Windows validation is not established. | Reproduce using pinned Bun in the stock smoke VM or hosted runner, resolve install failures, then run affected core/MCP/ripgrep/LSP/CLI suites and Windows browser gates. Do not call this quota without evidence. | +| CI-02 / P1 | Both old PR heads have red GitHub checks. Local Linux full tests pass, but that does not establish hosted or Windows parity. | Inspect new-head jobs and logs. Require compiler, unit and browser failures to be fixed; document decent local equivalents only for demonstrated infrastructure/config/quota problems. | +| SYNC-01 / P1 | Latest fetched upstream dev and production are `e2894562f8ba943d72172d10b727c24d5f650c16`; integration branches contain `70b4ca8c181e4c1ac6d8993b86249d824487ec65`. The extra commit changes console usage normalization/tier configuration, not the web session fixes. | Merge this and any later upstream changes into both integration branches with affected console validation; recheck ancestry before merge. This checkpoint does not claim latest-upstream completion. | +| RELEASE-01 / P1 | Owner-authorized merge accepts the documented failures; Basic-auth deployed-browser parity and final exact-head full browser validation remain outstanding. | Complete the backlog in follow-up PRs and rerun both branches. Reinstall/deploy separately when explicitly requested. | + +### Reproduction commands + +From `packages/app`, against the task-owned mock-test Vite server (currently +14449, backend request origin 14999): + +```sh +PLAYWRIGHT_BASE_URL=http://127.0.0.1:14449 PLAYWRIGHT_PORT=14449 \ +PLAYWRIGHT_SERVER_PORT=14999 PLAYWRIGHT_WORKERS=2 bun run test:e2e \ + e2e/regression/session-rollback-queue.spec.ts \ + e2e/regression/review-terminal-stacked.spec.ts \ + e2e/user-story/model-selection-flow.spec.ts \ + e2e/smoke/session-timeline.spec.ts +``` + +The tests install synthetic API routes; these ports are not the deployed service. +If no task server is listening, use the repository Playwright configuration's own +server lifecycle on an unused port. Never restart the user's service for this. + +Production benchmark (builds its own temporary preview; run without other test +workloads): + +```sh +PLAYWRIGHT_PORT=14448 PLAYWRIGHT_WORKERS=1 bun run test:e2e \ + --config e2e/performance/playwright.config.ts --project=chromium \ + e2e/performance/timeline/session-tab-switch-benchmark.spec.ts +``` + +### Checkpoint performance result — CPU-throttled comparison + +The final production-build benchmark passed its two structural test cases, but +**did not establish performance parity**. V2 median stable times (five samples per +scenario) increased relative to the earlier baseline: + +| Scenario | Earlier baseline (ms) | Checkpoint (ms) | +| ------------------- | --------------------: | --------------: | +| Review closed, cold | 136.2 | 406.0 | +| Review closed, hot | 103.7 | 314.0 | +| Review open, cold | 120.6 | 354.2 | +| Review open, hot | 112.6 | 324.0 | + +No wrong-destination or review-file-host replacement samples were observed. The +owner subsequently confirmed that CPU throttling caused the slower timings. +The focused browser run finished before benchmark sampling; its tail overlapped +the benchmark build startup. Because CPU conditions differed, these measurements +cannot establish either performance parity or a code-induced regression. + +**PERF-01 / P2 (follow-up):** rerun baseline and checkpoint alternately with matching +CPU throttling and machine load. Retain first-frame/latest-message and review-host +identity checks. Investigate code only if a slowdown reproduces under comparable +conditions; the throttled comparison is not a merge blocker. From bbd72fb8b0bb6de580d2041a0150016227c63ac0 Mon Sep 17 00:00:00 2001 From: Stefan Avram <98915060+Slickstef11@users.noreply.github.com> Date: Sat, 5 Sep 2026 09:51:32 -0400 Subject: [PATCH 024/129] fix(console): connect enterprise form to Chatwoot (#47488) Co-authored-by: slickstef11 --- infra/console.ts | 2 ++ packages/console/app/src/routes/api/enterprise.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/infra/console.ts b/infra/console.ts index 764807d978fb..44d82a2fb0d9 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -244,6 +244,7 @@ const bucketNew = new sst.cloudflare.Bucket("ZenDataNew") const DISCORD_INCIDENT_WEBHOOK_URL = new sst.Secret("DISCORD_INCIDENT_WEBHOOK_URL") const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID") const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY") +const ENTERPRISE_SALES_INBOX_EMAIL = new sst.Secret("ENTERPRISE_SALES_INBOX_EMAIL") const SALESFORCE_CLIENT_ID = new sst.Secret("SALESFORCE_CLIENT_ID") const SALESFORCE_CLIENT_SECRET = new sst.Secret("SALESFORCE_CLIENT_SECRET") @@ -273,6 +274,7 @@ new sst.cloudflare.x.SolidStart("Console", { EMAILOCTOPUS_API_KEY, AWS_SES_ACCESS_KEY_ID, AWS_SES_SECRET_ACCESS_KEY, + ENTERPRISE_SALES_INBOX_EMAIL, SALESFORCE_CLIENT_ID, SALESFORCE_CLIENT_SECRET, SALESFORCE_INSTANCE_URL, diff --git a/packages/console/app/src/routes/api/enterprise.ts b/packages/console/app/src/routes/api/enterprise.ts index ff8f229e8b9e..65f5c72b2311 100644 --- a/packages/console/app/src/routes/api/enterprise.ts +++ b/packages/console/app/src/routes/api/enterprise.ts @@ -98,7 +98,7 @@ ${body.phone ? `${body.phone}
` : ""}`.trim() return false }), AWS.sendEmail({ - to: "contact@anoma.ly", + to: Resource.ENTERPRISE_SALES_INBOX_EMAIL.value, subject: `Enterprise Inquiry from ${body.name}`, body: emailContent, replyTo: body.email, From 7e9140832eb69f2aa85746f67fe9ac5854065a17 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 11:02:22 -0300 Subject: [PATCH 025/129] chore: keep transient audit notes outside the repository --- docs/fork/web-audit.md | 437 ----------------------------------------- 1 file changed, 437 deletions(-) delete mode 100644 docs/fork/web-audit.md diff --git a/docs/fork/web-audit.md b/docs/fork/web-audit.md deleted file mode 100644 index 9c4e4a9ea2fc..000000000000 --- a/docs/fork/web-audit.md +++ /dev/null @@ -1,437 +0,0 @@ -# Fork web compatibility audit - -Updated: 2026-09-05. - -## Release decision - -**Merge authorized with known follow-ups.** On 2026-09-05 the repository owner -explicitly requested committing and pushing every current improvement and merging -production PR #7 and development PR #8, with remaining issues handled in follow-up -PRs. This supersedes the earlier draft/release hold. It does not mean the full -browser suite or hosted CI is green. The checkpoint below records the evidence -and unresolved work without weakening or skipping failing tests. - -The owner also confirmed that the CPU was throttled during the slower benchmark. -Those timings are not comparable to the earlier unthrottled baseline and are not -established as a code-induced performance regression. - -Integrated upstream remains `70b4ca8c181e4c1ac6d8993b86249d824487ec65`; -newer upstream work is explicitly tracked below. No finite audit establishes that -the entire fork is bug-free. - -The deployed service on ports 4096/14096 and its binary were not changed. Tests -used separate worktrees, loopback servers, synthetic sessions and isolated XDG -data. Real-server smoke checks were unauthenticated; they do not establish -Basic-auth deployment parity. The replacement transport has a separate unit -test asserting Basic-auth headers with dummy credentials. - -## Comparison boundary - -| Ref | Commit | -| ------------------------------------ | ------------------------------------------ | -| Fork production baseline | `c94eb6133eca258cb06cc30d159aae7a76519d1b` | -| Fork development baseline | `722450048f` | -| Initial upstream production | `20a7743876` | -| Initial upstream development | `79903a4cf7` | -| Upstream integrated in both branches | `70b4ca8c181e4c1ac6d8993b86249d824487ec65` | - -The initial production delta was 510 files, 38,512 additions / 7,297 deletions. -The latest upstream merge did not change app/session-ui runtime source relative -to the initial upstream production snapshot. It did bring release, provider and -console changes, so both integration branches received full unit/type validation. - -Fork-heavy boundaries remain the current-session model/reducer, timeline -presentation, prompt submit path, session queue, model persistence and shared -message-part rendering. These are intentional features, not candidates for -wholesale upstream replacement. App code still composes a pinned promise client, -a separate pinned current-session client, generated legacy SDK, and compatibility -stores. Regenerating the workspace client alone does not update the pinned clients. - -### Functional comparison - -Pristine latest upstream passed all 10 selected Chromium tests: reasoning-selector -visibility, eight reasoning/timeline profiles, and review-state persistence. -The fork passes those corresponding contracts with its native-session fixtures, -plus its additional queue/replacement/pending/model-selection regressions. - -An attempted identical-fixture comparison was invalid: fork-only payload fields -failed upstream schema validation and the upstream UI did not subscribe to the -fork's native session-event endpoint. Those failures are **not** counted as -upstream product bugs. Compare observable contracts using each protocol's valid -fixtures; do not silently rewrite either runtime to fit the other's mocks. - -## Reproduced defects and fixes - -### 1. Admitted steering disappeared after reload - -Admission is durable but transcript promotion occurs later at a runner boundary. -The web snapshot lacked pending admissions; replay starts at the snapshot's -watermark, so already-admitted input could not be reconstructed from later events. - -The message endpoint now includes an optional pending-user projection, selected -from unpromoted, undiscarded steering rows. Queue inputs remain separate. The -current-session model hydrates that projection and ignores stale pending snapshots -that would resurrect discarded input. Neither this query nor stop promotes or -resumes input. New mapping logic is isolated in `pending-inputs.ts`. - -Evidence: database selection/interrupt tests, reducer stale-snapshot test, model -hydration test, cross-browser reload test, and an isolated real-server interrupt -followed by browser reload. Admitted text remained visible and Stop was absent -when execution was idle. - -### 2. Rollback editing disagreed with upstream revert semantics - -Upstream revert retains its boundary message. The fork web editor hides that -message and means to replace it. Pending admissions also are not projected -message boundaries. Passing an extra flag to the pinned promise client was not -sufficient: its encoder omitted the unknown field. - -Inclusive replacement is now explicit and opt-in. Default upstream behavior is -unchanged. Staging captures an admission cutoff without deleting input. Commit -removes the selected original and subsequent pre-cutoff steering, preserves -explicit pending queues, and preserves the replacement admitted after staging. -The runner still commits before promoting new input. The focused persistence -logic lives in `revert-replacement.ts`; `server-revert.ts` bridges only the opt-in -request through the generated SDK. Clients were regenerated by repository scripts. - -The current-session projection is also updated optimistically and refreshed after -commit. Rollback no longer continues after an interruption error; it restores the -draft and shows a request-failed notification. Both composer implementations send -the explicit replacement flag on Enter. - -Evidence: 106 runner/projector/replacement tests passed, including default upstream -boundary retention. The live API check staged a pending original, admitted an -edited replacement, committed, and returned only the edited pending message. -Cross-browser tests cover Enter replacement, retained queue, and interruption -failure. Multi-client concurrent stage/resume remains a coverage gap. - -### 3. Review metadata and diff routes drifted across clients - -Current-session protocol detection did not imply support for `/api/vcs`. The -isolated server returned UI HTML for that unsupported path, and bootstrap skipped -legacy branch metadata on V2. The fix reuses the existing VCS compatibility adapter -and loads branch metadata from the supported endpoint. No new parallel VCS -implementation was introduced. - -Evidence: transport tests assert `/vcs/diff`, working-to-git mode translation, -directory placement and returned content; bootstrap tests assert branch metadata; -review persistence passes in both browsers. Review-line fixtures were corrected -to observe the actual compatibility endpoint rather than a nonexistent V2 route. - -### 4. Switching models reset timeline scroll - -The recent-model resource used a suspending read. A selection changed recent -models and briefly detached the session subtree, resetting the same scroll node -to zero. Browser instrumentation confirmed removal/reinsertion under `main`, -rather than a new session or new scroll node. The resource code is shared with -upstream; this is not evidence that every upstream UI configuration exhibits it. - -A one-line read of the resource's latest value prevents that suspension without -restructuring the layout. All six Chromium/Firefox model-switch scroll regressions -pass, including variant/no-variant changes and unchanged composer dimensions. - -### 5. Test and CI drift obscured regressions - -Mocks dropped model variants, assigned obsolete release dates, ignored configured -agents, lacked context/pending snapshots, and left rename responses non-mutating. -Several tests imported a removed pagination helper or asserted legacy part IDs -against normalized native rendering. Other fixtures hardcoded the local server -port, defeating isolated-port runs. Corrected fixtures retain semantic assertions; -no failing tests were skipped or weakened to force a green result. - -Three HTTP API exerciser assertions expected raw project values while the declared -Effect API uses located envelopes. Assertions now validate location/project and -the nested data explicitly. Coverage/auth/effect runs each passed 236 scenarios, -with no missing or skipped route scenarios. - -Fork CI previously selected upstream-only Blacksmith runners. It now selects -GitHub-hosted runners on the fork, checks production as well as dev, and typechecks -the addressed browser fixtures. Scheduled upstream sync now runs validation before -pushing because its GITHUB_TOKEN push does not trigger normal push workflows. -Actionlint syntax validation passed; optional shellcheck still reports existing -SC2129 style findings in the sync script, not new syntax errors. - -### 6. Firefox could not open the tab context menu with Shift+F10 - -A focused tab received both keydown events but no native `contextmenu` event. -The Kobalte trigger handles `contextmenu` and pointer events, not these keyboard -keys. The tab now explicitly routes Shift+F10 and ContextMenu to that existing -trigger, anchored below the focused tab. The helper is isolated in the existing -fork tab-gesture module; editing/dragging guards stay at the integration point. -Preventing the default avoids duplicate native synthesis on other browsers. - -Evidence: repeated Firefox failure before the fix, keyboard-event instrumentation, -three new unit cases, and all 16 rename tests passing in Chromium/Firefox. The -keyboard assertion was not replaced with a pointer click or skipped. - -### 7. Context circle counted messages hidden by staged rollback - -The message prop fix resolved the always-zero circle, but a second discrepancy -remained: the detail screen selects context through the staged revert boundary; -the circle used the full context snapshot. Staging does not delete that snapshot. -The circle now reuses `selectSessionContextMessages`, the same existing helper as -the detail screen. This is one import and one targeted call-site integration. - -Evidence: a schema-validated browser fixture records 50,000 of 100,000 tokens before -rollback and 25,000 after rollback. The rollback circle failed at 50% before this -fix. All four Chromium/Firefox cases now assert the SVG's numeric progress, -tooltip token count, and detail-screen total/percentage against explicit values. - -## Validation record - -- Both latest-upstream integration branches: **30/30 typecheck tasks and 10/10 - full unit-test tasks passed**. Each core suite: 1,142 tests. Each opencode suite: - 3,650 tests. Latest app suite: 793 tests. Generated-client check passed. -- Focused fork browser run: **32/32 passed** across Chromium and Firefox; separate - model-switch scroll run: **6/6 passed**. -- Initial full browser run: **135 passed, 96 failed, 3 pre-existing skips**. - Latest full rerun: **178 passed, 55 failed, 3 pre-existing skips** (236 cases, - 6.3 minutes). This rerun includes keyboard/remote-fixture fixes and precedes the - final context-selection fix; the latter separately passed all four new cases. - Neither run is an all-green result. -- Rename tests: **16/16 passed** across browsers after correcting native rename - fixtures and keyboard menu activation. Remote settings/auto-accept tests: - **4/4 passed**, including unfocused parent/child sessions on a different server. - The actual current request query is `location[directory]`, not `directory`; - current replies are session-addressed and use `{ reply: "once" }`. -- CI is not green. Windows development jobs failed before tests during Bun 1.3.14 - patch installation (`ENOTEMPTY` for patched `@ai-sdk/openai-compatible`), even - with no restored cache. Linux development unit CI hit subprocess timeouts in - `run-process.test.ts`; local full runs passed. Production typechecking was - cancelled, not a demonstrated compiler error. These require investigation and - successful reruns; no timeouts or assertions have been weakened. -- Manual browser against isolated source backend: edited pending message visible, - original absent, no Stop while idle; Muse Spark 1.3 Free offered Default, - Minimal, Low, Medium, High and Xhigh reasoning choices. No paid inference used. -- Production tab-switch benchmark: both before/after runs passed. V2 median stable - times, milliseconds: - - | Scenario | Before | After | - | ------------------- | -----: | ----: | - | Review closed, cold | 136.2 | 142.8 | - | Review closed, hot | 103.7 | 115.1 | - | Review open, cold | 120.6 | 120.4 | - | Review open, hot | 112.6 | 113.4 | - - Five local samples per scenario, not a statistical performance guarantee. The - final after run includes latest integrated upstream, the recent-model fix, and - the context-selection fix. These samples include concurrent local browser work; - the increases are not established as code-induced regressions or dismissed as - harmless. Repeat isolated performance measurements before release. - No wrong-destination or review-host replacement samples were observed. - -## Historical full-suite failure inventory (before checkpoint) - -These are failing test cases, not 55 confirmed product bugs. Several fixtures still -send mutation arrays as one SSE event or assert obsolete part IDs. Repair invalid -fixtures without weakening observable behavior assertions; investigate failures -that remain against valid data. - -| Area | Cases | -| ---------------------------------- | ----: | -| Native timeline transport | 14 | -| Timeline projection | 6 | -| Smoke pagination/timeline | 6 | -| Collapse state | 4 | -| History-root transitions | 4 | -| Subagent navigation | 4 | -| Context resize | 3 | -| Lifecycle/retry | 3 | -| Request docks | 2 | -| Reducer projection | 2 | -| Shell outline | 2 | -| Todo navigation | 2 | -| New-project model-selection story | 2 | -| Review/terminal stacking (Firefox) | 1 | - -## Historical work plan (superseded by checkpoint backlog) - -1. [x] Correct remote settings fixtures and verify cross-server auto-accept, - including unfocused parent/child sessions. This was fixture protocol drift, - not evidence of a runtime permissions defect. -2. [x] Resolve Firefox keyboard context-menu opening while retaining the keyboard - assertion. Rename, tab close and focus restoration pass on both browsers. -3. Migrate remaining timeline transport fixtures to individual native events and - native normalized part identities. Audit actual render behavior for collapse, - retry, context resize, history root, comments, attachments and subagent cards. -4. Finish request-dock, todo, smoke pagination and new-project/model user-story - validation. All configured test ports must be honored. Extend e2e typechecking - to the remaining regression files; current coverage is intentionally enumerated, - not a claim that every e2e file typechecks. -5. Audit keyboard undo/redo interruption-error handling and compatibility-store - reads. Main rollback is fixed; other entrypoints must receive equivalent tests. - Add multi-client replacement/cutoff tests. Basic and staged-rollback context - usage browser parity is now covered; live usage updates remain to be audited. -6. Run the complete browser suite and final benchmark on both integration heads; - inspect Linux and Windows CI. Update the PR validation record with final results. -7. Re-fetch upstream, validate any additional commits, then merge both PRs and - verify upstream ancestry on dev and production. - Do not deploy or reinstall the user's running service as an incidental step. - -## Mergeability rule - -Keep fork behavior behind focused modules and optional protocol fields. Prefer -small call-site adapters over editing vendored client archives or broadly rewriting -upstream UI. Preserve upstream defaults. Every future sync must validate these -boundaries, not merely resolve textual merge conflicts. - -## Saved-work checkpoint — 2026-09-05 - -This is a reviewed **checkpoint with known unresolved issues**. The owner has -authorized merging it and deferring the remaining work to follow-up PRs. No runtime features were removed to make the upstream diff smaller. -The changes use existing adapters and optional call-site callbacks; they do not -replace the session architecture. The user's live service and installed binary -remain untouched. - -### Additional underlying fixes saved - -- **Stop before a provider turn:** interruption could succeed before any assistant - step existed, leaving no step-ended event to clear the composer's busy state. - After a successful interrupt, both composers now refresh the current session. - Queue draining is paused first. Admitted steering is neither deleted nor - resubmitted. The new browser case failed before this change and passes after it, - including reload. The submit test asserts pause → interrupt → refresh ordering. -- **Native user projection:** compatibility conversion now preserves native file - URIs, agent mentions and original typed payload parts, including synthetic - comment context. Native rich prompts no longer depend on lossy legacy fields. -- **Child-task navigation:** native task metadata uses `sessionID`, while legacy - cards read `sessionId`. The adapter supplies the alias without replacing an - existing legacy value, and reads native structured metadata. Child headings - resolve descriptions from the parent's cached parts, not the child-only current - message accessor. -- **Retry display:** the current-to-compatibility status bridge now includes retry - metadata and updates attempts even when the status tag has not changed. Native - retry → recovery → idle browser coverage asserts both attempts. -- **Supported shared endpoints:** V2 session detection no longer forces empty - todos or path metadata. The existing todo endpoint remains authoritative for - persisted tasks; live updates and forced refresh retain their existing behavior. - Path lookup retains an empty fallback only when unavailable on a V2 server. -- **Keyboard rollback integration:** Undo/Redo now receive current user messages, - including pending steering, and delegate to the same rollback/restore mutations - as the timeline. Completion is guarded against session navigation. Undo reaches - the correct pending draft in the new regression; the full Redo interaction is - still blocked by the failure below and is not claimed fixed end-to-end. -- **Provider discovery:** onboarding choices come from the integration catalogue, - separately from the connected-model catalogue. Missing/offline responses are - tolerated; models and connected/default selections are not invented. This fixes - the discovery boundary but exposes a remaining connection-dialog hydration - defect; the whole onboarding flow is still incomplete. -- **Replacement consistency:** two independent current-session clients converge - after a committed replacement. Persistence tests cover a steer exactly at the - admission cutoff, a later replacement, explicit queues, and an unrelated session. - -### Test-fixture and CI improvements saved - -Native timeline fixtures now send individual events with the correct aggregate -ID, durable sequence, normalized text/reasoning IDs and current endpoint envelopes. -Reconnect tests assert the durable `after` cursor. Pagination distinguishes the -current 100-message page from compatibility hydration. Browser assertions still -check behavior, ordering, geometry, caret restoration and error absence; failing -cases were not skipped or given relaxed assertions. Expanded e2e typechecking is -explicitly enumerated, not comprehensive coverage of every Playwright file. - -The shared Bun setup action now honors its caller's Node version, so the existing -Playwright Node 24.15 pin is no longer silently overwritten. Fork CI uses lower -concurrency; upstream workflow concurrency is retained. CLI tests cap concurrent -subprocess tests at two without increasing their timeouts. Windows fixtures use -portable paths and shell syntax, ripgrep is preinstalled, and Turbo forwards the -existing isolated-home/file-watcher environment into core tests. - -The earlier GitHub failures were **not established as quota exhaustion**: - -- Typecheck jobs exited 137 or were cancelled without TypeScript diagnostics. - Resource pressure is plausible, but OOM was not proven. -- Linux subprocess timeout failures reproduced locally with cold concurrent - compilation. Pinned Bun 1.3.14 with concurrency two passed all 14 CLI cases with - the same deadlines (49 assertions). -- Windows showed real portability/download issues as well as a distinct patched - Bun dependency installation `ENOTEMPTY` failure. The portability changes are - tested on Linux; a clean Windows pass is still required. A stock Windows smoke - VM was used for investigation, but its attempted install is not a passing result. - -### Checkpoint verification - -Commands below run from the named package directory, never root `bun test`. - -| Validation | Observed result | -| ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -| App `bun run test` | 800 unit + 51 browser-environment unit tests passed; zero failures | -| App `bun typecheck` and `bun run typecheck:e2e` | Both passed | -| Core `bun test test/command.test.ts test/session-prompt.test.ts test/session-replacement.test.ts` | 38 passed, 100 assertions | -| Timeline fixture unit test | 4 passed, 8 assertions | -| Workflow `actionlint` (test and typecheck) | Passed | -| Full pinned-Bun Linux Turbo unit run before the final focused additions | 10/10 tasks passed, uncached, 14m43s; core 1,142 tests; opencode 3,627 pass / 22 skip / 1 todo | -| Seven timeline projection/lifecycle/geometry specs, Chromium + Firefox | 43 passed; 3 existing Firefox CDP skips | -| Stop, pending reload, rollback and native transport focused run | 24 passed, 2 failed; only command Undo/Redo failed | -| Recorded, rolled-back and live context usage | 6 passed across Chromium + Firefox | -| Fresh request/todo/child/review/onboarding/smoke run | 18 passed, 8 failed across Chromium + Firefox | - -These are separate runs with some overlapping cases, not an aggregate full-suite -pass. The fresh 26-case run confirms request-dock caret, todo lifecycle, child -navigation, prepend anchoring and cold-tab paint behavior. Its eight failures are -listed below. A final full browser run on both exact branch heads remains follow-up work. - -### Actionable backlog for follow-up PRs - -| ID / priority | Evidence and likely boundary | Required next step / exit criterion | -| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| WEB-01 / P1 | New-project OpenCode Go discovery succeeds, but selecting it throws `Cannot read properties of undefined (reading 'name')` in `ProviderConnection` / `MethodSelection`, before the API-key field appears (both browsers). `provider()` assumes that a new hook instance already has either integration choices or a connected provider. | Make connection rendering safe during catalogue hydration without inventing connected models. Add a delayed-catalogue regression and complete key submission, refresh and model selection in both browsers. | -| WEB-02 / P1 | Command Undo selects/stages the pending message correctly; the subsequent Ctrl+P for Redo never produces a dialog textbox in either browser. No error page in the clean repro. | Separate command-dialog/focus teardown from a runtime hotkey defect; retain real keyboard activation. Require stage, clear, empty draft and restored single message assertions to pass. | -| WEB-03 / P2 | Review/terminal stacking times out waiting for a tree to have positive height in both browsers in the fresh run. Native lifecycle events are now used. | Inspect layout readiness and fixture review mode before changing runtime. Verify tree/terminal geometry, scrolling, detail refresh and remount invariants, without fixed sleep inflation. | -| WEB-04 / P2 | Cached-tab smoke reports one removed first-paint plain text `` in both browsers; latest-message and bottom placement assertions already pass. Probe currently tracks every descendant, including `HighlightedText` leaves rebuilt during hydration. | Establish whether a user-visible row/part remount occurs. Preserve semantic row identity and first-frame assertions; distinguish incidental leaf updates from structural replacement with a dedicated repro rather than blindly relaxing zero-removal. | -| WEB-05 / P2 | Full-history smoke reaches its final error audit, then fails: Chromium logs a current-context transport error; Firefox logs a global event-stream failure. No forbidden text or error toast was observed. | Determine mock coverage/reconnect or cancellation logging versus a real transport failure; preserve the no-console-errors contract and full ordering checks. | -| CI-01 / P1 | Windows patched dependency install failed with `ENOTEMPTY`; clean Windows validation is not established. | Reproduce using pinned Bun in the stock smoke VM or hosted runner, resolve install failures, then run affected core/MCP/ripgrep/LSP/CLI suites and Windows browser gates. Do not call this quota without evidence. | -| CI-02 / P1 | Both old PR heads have red GitHub checks. Local Linux full tests pass, but that does not establish hosted or Windows parity. | Inspect new-head jobs and logs. Require compiler, unit and browser failures to be fixed; document decent local equivalents only for demonstrated infrastructure/config/quota problems. | -| SYNC-01 / P1 | Latest fetched upstream dev and production are `e2894562f8ba943d72172d10b727c24d5f650c16`; integration branches contain `70b4ca8c181e4c1ac6d8993b86249d824487ec65`. The extra commit changes console usage normalization/tier configuration, not the web session fixes. | Merge this and any later upstream changes into both integration branches with affected console validation; recheck ancestry before merge. This checkpoint does not claim latest-upstream completion. | -| RELEASE-01 / P1 | Owner-authorized merge accepts the documented failures; Basic-auth deployed-browser parity and final exact-head full browser validation remain outstanding. | Complete the backlog in follow-up PRs and rerun both branches. Reinstall/deploy separately when explicitly requested. | - -### Reproduction commands - -From `packages/app`, against the task-owned mock-test Vite server (currently -14449, backend request origin 14999): - -```sh -PLAYWRIGHT_BASE_URL=http://127.0.0.1:14449 PLAYWRIGHT_PORT=14449 \ -PLAYWRIGHT_SERVER_PORT=14999 PLAYWRIGHT_WORKERS=2 bun run test:e2e \ - e2e/regression/session-rollback-queue.spec.ts \ - e2e/regression/review-terminal-stacked.spec.ts \ - e2e/user-story/model-selection-flow.spec.ts \ - e2e/smoke/session-timeline.spec.ts -``` - -The tests install synthetic API routes; these ports are not the deployed service. -If no task server is listening, use the repository Playwright configuration's own -server lifecycle on an unused port. Never restart the user's service for this. - -Production benchmark (builds its own temporary preview; run without other test -workloads): - -```sh -PLAYWRIGHT_PORT=14448 PLAYWRIGHT_WORKERS=1 bun run test:e2e \ - --config e2e/performance/playwright.config.ts --project=chromium \ - e2e/performance/timeline/session-tab-switch-benchmark.spec.ts -``` - -### Checkpoint performance result — CPU-throttled comparison - -The final production-build benchmark passed its two structural test cases, but -**did not establish performance parity**. V2 median stable times (five samples per -scenario) increased relative to the earlier baseline: - -| Scenario | Earlier baseline (ms) | Checkpoint (ms) | -| ------------------- | --------------------: | --------------: | -| Review closed, cold | 136.2 | 406.0 | -| Review closed, hot | 103.7 | 314.0 | -| Review open, cold | 120.6 | 354.2 | -| Review open, hot | 112.6 | 324.0 | - -No wrong-destination or review-file-host replacement samples were observed. The -owner subsequently confirmed that CPU throttling caused the slower timings. -The focused browser run finished before benchmark sampling; its tail overlapped -the benchmark build startup. Because CPU conditions differed, these measurements -cannot establish either performance parity or a code-induced regression. - -**PERF-01 / P2 (follow-up):** rerun baseline and checkpoint alternately with matching -CPU throttling and machine load. Retain first-frame/latest-message and review-host -identity checks. Investigate code only if a slowdown reproduces under comparable -conditions; the throttled comparison is not a merge blocker. From e178dbff5986fffce863d3b03ab66869b5d895b5 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 11:34:41 -0300 Subject: [PATCH 026/129] fix(app): preserve hydrated dialogs and cached panel rendering --- .../regression/session-rollback-queue.spec.ts | 4 +++ .../app/e2e/smoke/session-timeline.spec.ts | 17 +--------- .../user-story/model-selection-flow.spec.ts | 21 ++++++++++-- .../components/dialog-connect-provider.tsx | 4 ++- packages/app/src/components/file-tree-v2.tsx | 5 +-- .../components/virtual-scroll-element.test.ts | 32 ++++++++++++++++++- .../src/components/virtual-scroll-element.ts | 26 +++++++++++++++ .../pages/session/v2/session-file-list-v2.tsx | 5 +-- .../src/components/message-part.tsx | 4 +-- 9 files changed, 91 insertions(+), 27 deletions(-) diff --git a/packages/app/e2e/regression/session-rollback-queue.spec.ts b/packages/app/e2e/regression/session-rollback-queue.spec.ts index 80880719e8cc..0935d7e586b2 100644 --- a/packages/app/e2e/regression/session-rollback-queue.spec.ts +++ b/packages/app/e2e/regression/session-rollback-queue.spec.ts @@ -181,11 +181,15 @@ test("command undo and redo use the current pending input and replacement draft" await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) await expectSessionTitle(page, session.title) await expect(page.getByText("second user prompt", { exact: true })).toHaveCount(1) + await page.clock.install() const select = async (command: string) => { await page.keyboard.press("Control+p") const dialog = page.getByRole("dialog") await dialog.getByRole("textbox").fill(command) await dialog.getByText(command, { exact: true }).click() + // Dialog portals disappear before the 100ms close timer releases the command lock. + await page.clock.runFor(150) + await expect(page.locator("[data-dialog-layer]")).toHaveCount(0) } const input = page.locator('[data-component="prompt-input"]') await select("Undo") diff --git a/packages/app/e2e/smoke/session-timeline.spec.ts b/packages/app/e2e/smoke/session-timeline.spec.ts index b1410cf4c7e7..f886f907e674 100644 --- a/packages/app/e2e/smoke/session-timeline.spec.ts +++ b/packages/app/e2e/smoke/session-timeline.spec.ts @@ -3,7 +3,7 @@ import { base64Encode } from "@opencode-ai/core/util/encode" import { currentPageMessages, fixture } from "./session-timeline.fixture" import { trackPageErrors, expectNoSmokeErrors } from "../utils/errors" import { mockOpenCodeServer } from "../utils/mock-server" -import { APP_READY_TIMEOUT, expectAppVisible, expectSessionTitle } from "../utils/waits" +import { expectAppVisible, expectSessionTitle } from "../utils/waits" import { expectAtBottom, scrollToBottom } from "../utils/scroll" const forbiddenText = ["Load details", "Show earlier steps"] @@ -383,9 +383,6 @@ test.describe("smoke: session timeline", () => { }) await configureSmokePage(page, fixture.directory) - await selectHomeProject(page, fixture.project.name) - await navigateToSession(page, fixture.directory, fixture.sourceID, fixture.expected.sourceTitle) - await expectSessionReady(page) await navigateToSession(page, fixture.directory, fixture.targetID, fixture.expected.targetTitle) const expectedPartIDs = fixture.expected.targetPartIDs const expectedMessageIDs = fixture.expected.targetMessageIDs @@ -763,18 +760,6 @@ function expectCompleteScroll( expect(expectedPartIDs.length).toBe(331) } -async function selectHomeProject(page: Page, projectName: string) { - await page.goto("/") - const row = page - .locator('[data-component="home-project-row"]') - .filter({ hasText: new RegExp(projectName, "i") }) - .first() - await expectAppVisible(row) - await row.click() - await expect(row).toHaveAttribute("data-selected", "", { timeout: APP_READY_TIMEOUT }) - await expect(page).toHaveURL(/\/$/) -} - async function navigateToSession(page: Page, directory: string, sessionId: string, expectedTitle: string) { await page.goto(`/${base64Encode(directory)}/session/${sessionId}`) await expectSessionTitle(page, expectedTitle) diff --git a/packages/app/e2e/user-story/model-selection-flow.spec.ts b/packages/app/e2e/user-story/model-selection-flow.spec.ts index 76581a5587e9..4c1a4b49c9f2 100644 --- a/packages/app/e2e/user-story/model-selection-flow.spec.ts +++ b/packages/app/e2e/user-story/model-selection-flow.spec.ts @@ -5,7 +5,12 @@ import { expectAppVisible } from "../utils/waits" const directory = "C:/OpenCode/NewProject" test("creates a session in a new project, connects OpenCode Go, and selects its model", async ({ page }) => { + const errors: string[] = [] + page.on("pageerror", (error) => errors.push(error.message)) let connectedGo = false + let delayCatalogue = false + const catalogue = Promise.withResolvers() + const requested = Promise.withResolvers() const connections: Array<{ integrationID: string; body: unknown }> = [] await mockOpenCodeServer(page, { @@ -61,8 +66,12 @@ test("creates a session in a new project, connects OpenCode Go, and selects its }) await page.route( (url) => url.pathname === "/api/integration", - (route) => - route.fulfill({ + async (route) => { + if (delayCatalogue) { + requested.resolve() + await catalogue.promise + } + return route.fulfill({ contentType: "application/json", body: JSON.stringify({ location: { directory }, @@ -71,7 +80,8 @@ test("creates a session in a new project, connects OpenCode Go, and selects its { id: "opencode-go", name: "OpenCode Go", methods: [{ type: "key" }], connections: [] }, ], }), - }), + }) + }, ) await page.addInitScript(() => { localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } })) @@ -91,7 +101,11 @@ test("creates a session in a new project, connects OpenCode Go, and selects its await modelControl.click() await expect(page.locator('[data-section="free-models"]')).toContainText("Free models provided by OpenCode") + delayCatalogue = true await page.locator('[data-provider-id="opencode-go"]').click() + await requested.promise + expect(errors).toEqual([]) + catalogue.resolve() await page.locator('[data-input="provider-api-key"]').fill("mock-go-api-key") await page.locator('[data-action="provider-connect-submit"]').click() await expect(page.locator('[data-component="dialog-v2"]')).toHaveCount(0) @@ -104,4 +118,5 @@ test("creates a session in a new project, connects OpenCode Go, and selects its await goModel.click() await expect(modelControl).toContainText("Go Model 1") + expect(errors).toEqual([]) }) diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx index 1081310e5f21..5a7c1d2bcd8f 100644 --- a/packages/app/src/components/dialog-connect-provider.tsx +++ b/packages/app/src/components/dialog-connect-provider.tsx @@ -408,7 +408,9 @@ function ProviderConnection(props: { }) const provider = createMemo( - () => providers.all().get(props.provider) ?? serverSync().data.provider.all.get(props.provider)!, + () => + providers.all().get(props.provider) ?? + serverSync().data.provider.all.get(props.provider) ?? { id: props.provider, name: props.provider }, ) const fallback = createMemo(() => [ { diff --git a/packages/app/src/components/file-tree-v2.tsx b/packages/app/src/components/file-tree-v2.tsx index 15218a7cd14a..0983cf93ba41 100644 --- a/packages/app/src/components/file-tree-v2.tsx +++ b/packages/app/src/components/file-tree-v2.tsx @@ -23,7 +23,7 @@ import { normalizeFileTreeV2Path, type FileTreeV2Node, } from "@/components/file-tree-v2-model" -import { virtualScrollElement } from "@/components/virtual-scroll-element" +import { createVirtualScrollElement } from "@/components/virtual-scroll-element" export type { Kind } from "@/components/file-tree" @@ -142,12 +142,13 @@ export default function FileTreeV2(props: { return flattenFileTreeV2(model()!, expanded) }) const [root, setRoot] = createSignal() + const scrollElement = createVirtualScrollElement(root) const [focused, setFocused] = createSignal() const virtualizer = createVirtualizer({ get count() { return rows().length }, - getScrollElement: () => virtualScrollElement(root()), + getScrollElement: scrollElement, initialRect: { width: 0, height: 600 }, estimateSize: () => 28, gap: 2, diff --git a/packages/app/src/components/virtual-scroll-element.test.ts b/packages/app/src/components/virtual-scroll-element.test.ts index 20c25a8561a0..984831b4e746 100644 --- a/packages/app/src/components/virtual-scroll-element.test.ts +++ b/packages/app/src/components/virtual-scroll-element.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test" -import { virtualScrollElement } from "./virtual-scroll-element" +import { observeVirtualScrollElement, virtualScrollElement } from "./virtual-scroll-element" test("resolves the connected viewport that owns the virtual root", () => { const stale = document.createElement("div") @@ -16,3 +16,33 @@ test("resolves the connected viewport that owns the virtual root", () => { viewport.remove() expect(virtualScrollElement(root)).toBeNull() }) + +test("tracks late mounting, cached-panel reparenting, and disconnection", async () => { + const first = document.createElement("div") + const second = document.createElement("div") + first.className = second.className = "scroll-view__viewport" + const root = document.createElement("div") + first.append(root) + const updates: Array = [] + const dispose = observeVirtualScrollElement(root, (element) => updates.push(element)) + try { + expect(updates).toEqual([null]) + document.body.append(first, second) + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(updates.at(-1)).toBe(first) + second.append(root) + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(updates.at(-1)).toBe(second) + root.remove() + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(updates).toEqual([null, first, second, null]) + dispose() + first.append(root) + await new Promise((resolve) => setTimeout(resolve, 0)) + expect(updates).toHaveLength(4) + } finally { + dispose() + first.remove() + second.remove() + } +}) diff --git a/packages/app/src/components/virtual-scroll-element.ts b/packages/app/src/components/virtual-scroll-element.ts index 8708781d86a7..677de1886373 100644 --- a/packages/app/src/components/virtual-scroll-element.ts +++ b/packages/app/src/components/virtual-scroll-element.ts @@ -1,4 +1,30 @@ +import { createEffect, createSignal, onCleanup, type Accessor } from "solid-js" + export function virtualScrollElement(root: HTMLElement | undefined) { if (!root?.isConnected) return null return root.closest(".scroll-view__viewport") } + +export function observeVirtualScrollElement(root: HTMLElement, update: (element: HTMLDivElement | null) => void) { + let current = virtualScrollElement(root) + update(current) + // Cached panels can mount or move after Solid's onMount without changing their props. + const observer = new MutationObserver(() => { + const next = virtualScrollElement(root) + if (next === current) return + current = next + update(next) + }) + observer.observe(root.ownerDocument, { childList: true, subtree: true }) + return () => observer.disconnect() +} + +export function createVirtualScrollElement(root: Accessor) { + const [element, setElement] = createSignal(null) + createEffect(() => { + const current = root() + setElement(null) + if (current) onCleanup(observeVirtualScrollElement(current, setElement)) + }) + return element +} diff --git a/packages/app/src/pages/session/v2/session-file-list-v2.tsx b/packages/app/src/pages/session/v2/session-file-list-v2.tsx index 16cac2a45d66..472470072d54 100644 --- a/packages/app/src/pages/session/v2/session-file-list-v2.tsx +++ b/packages/app/src/pages/session/v2/session-file-list-v2.tsx @@ -5,7 +5,7 @@ import { createEffect, createMemo, createSignal, For, Show } from "solid-js" import { kindChange, kindLabel, type Kind } from "@/components/file-tree-v2" import { normalizePath } from "@/pages/session/v2/review-diff-kinds" import { createVirtualizer, defaultRangeExtractor } from "@tanstack/solid-virtual" -import { virtualScrollElement } from "@/components/virtual-scroll-element" +import { createVirtualScrollElement } from "@/components/virtual-scroll-element" // Drives the highlight/selection of the flat search-result list from the filter // input's keyboard events. @@ -53,12 +53,13 @@ export function SessionFileListV2(props: { const highlighted = () => normalizePath(props.highlighted ?? "") const normalized = createMemo(() => props.files.map(normalizePath)) const [root, setRoot] = createSignal() + const scrollElement = createVirtualScrollElement(root) const [focused, setFocused] = createSignal() const virtualizer = createVirtualizer({ get count() { return props.files.length }, - getScrollElement: () => virtualScrollElement(root()), + getScrollElement: scrollElement, initialRect: { width: 0, height: 600 }, estimateSize: () => 28, gap: 2, diff --git a/packages/session-ui/src/components/message-part.tsx b/packages/session-ui/src/components/message-part.tsx index 2e7a6b95f26c..8c100f327abe 100644 --- a/packages/session-ui/src/components/message-part.tsx +++ b/packages/session-ui/src/components/message-part.tsx @@ -423,7 +423,7 @@ function CurrentHighlightedText(props: { text: string; references: CurrentUserFi if (start < props.text.length) result.push({ text: props.text.slice(start) }) return result }) - return {(segment) => {segment.text}} + return {(segment) => {segment().text}} } export interface MessagePartProps { @@ -1672,7 +1672,7 @@ function HighlightedText(props: { text: string; references: FilePart[]; agents: return result }) - return {(segment) => {segment.text}} + return {(segment) => {segment().text}} } export function Part(props: MessagePartProps) { From 0f7ea20a1f6fd5e40972d0552ee87097c8f95c91 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 11:34:51 -0300 Subject: [PATCH 027/129] fix(ci): install verified ripgrep release on Windows --- .github/workflows/test.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f723fddc9464..c935fc964b1c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,7 +57,15 @@ jobs: - name: Install ripgrep if: runner.os == 'Windows' - run: choco install ripgrep --version 15.1.0 --yes --no-progress + shell: pwsh + run: | + $archive = Join-Path $env:RUNNER_TEMP "ripgrep.zip" + Invoke-WebRequest "https://github.com/BurntSushi/ripgrep/releases/download/15.1.0/ripgrep-15.1.0-x86_64-pc-windows-msvc.zip" -OutFile $archive + if ((Get-FileHash $archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne "124510b94b6baa3380d051fdf4650eaa80a302c876d611e9dba0b2e18d87493a") { + throw "Ripgrep checksum mismatch" + } + Expand-Archive $archive -DestinationPath "$env:RUNNER_TEMP/ripgrep" + "$env:RUNNER_TEMP/ripgrep/ripgrep-15.1.0-x86_64-pc-windows-msvc" >> $env:GITHUB_PATH - name: Cache Turbo uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 From f5973762e37a8cc802b86d6781be42d44b54d622 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 11:55:57 -0300 Subject: [PATCH 028/129] fix(app): isolate catalogue hydration and preserve admitted model state --- .../regression/session-context-usage.spec.ts | 163 +++++++++--------- .../user-story/model-selection-flow.spec.ts | 2 +- .../src/components/session-context-usage.tsx | 12 +- packages/app/src/hooks/use-providers.ts | 3 +- .../app/src/utils/session-message.test.ts | 27 +++ packages/app/src/utils/session-message.ts | 27 ++- 6 files changed, 140 insertions(+), 94 deletions(-) diff --git a/packages/app/e2e/regression/session-context-usage.spec.ts b/packages/app/e2e/regression/session-context-usage.spec.ts index 0461fc378597..29fdfc227209 100644 --- a/packages/app/e2e/regression/session-context-usage.spec.ts +++ b/packages/app/e2e/regression/session-context-usage.spec.ts @@ -33,89 +33,96 @@ const messages = [0, 1].flatMap((index) = ]) Schema.decodeUnknownSync(Schema.Array(SessionMessage.Message))(messages, { onExcessProperty: "error" }) -for (const mode of ["recorded", "rollback", "live"] as const) { - test(`context circle and detail agree with ${mode} usage`, async ({ page }) => { - const reverted = mode === "rollback" - let settled = mode !== "live" - const transport = await installSseTransport(page, { - server: `http://127.0.0.1:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`, - path: `/api/session/${sessionID}/event`, - }) - await mockOpenCodeServer(page, { - directory, - project: { id: "proj_context_usage", worktree: directory, time: { created: 1, updated: 1 }, sandboxes: [] }, - provider: { - all: [ +for (const newLayoutDesigns of [false, true]) { + for (const mode of ["recorded", "rollback", "live"] as const) { + test(`context circle and detail agree with ${mode} usage (${newLayoutDesigns ? "v2" : "legacy"})`, async ({ + page, + }) => { + await page.addInitScript((newLayoutDesigns) => { + localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns } })) + }, newLayoutDesigns) + const reverted = mode === "rollback" + let settled = mode !== "live" + const transport = await installSseTransport(page, { + server: `http://127.0.0.1:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`, + path: `/api/session/${sessionID}/event`, + }) + await mockOpenCodeServer(page, { + directory, + project: { id: "proj_context_usage", worktree: directory, time: { created: 1, updated: 1 }, sandboxes: [] }, + provider: { + all: [ + { + id: "opencode", + name: "OpenCode", + models: { test: { id: "test", name: "Test", limit: { context: 100_000 } } }, + }, + ], + connected: ["opencode"], + default: model, + }, + sessions: [ { - id: "opencode", - name: "OpenCode", - models: { test: { id: "test", name: "Test", limit: { context: 100_000 } } }, + id: sessionID, + directory, + title: "Context usage regression", + cost: 2.5, + time: { created: 1, updated: 5 }, + ...(reverted ? { revert: { messageID: "msg_3_user", inclusive: true } } : {}), }, ], - connected: ["opencode"], - default: model, - }, - sessions: [ - { - id: sessionID, - directory, - title: "Context usage regression", - cost: 2.5, - time: { created: 1, updated: 5 }, - ...(reverted ? { revert: { messageID: "msg_3_user", inclusive: true } } : {}), - }, - ], - currentPageMessages: () => ({ - items: messages - .map((message) => - !settled && message.type === "assistant" && message.id === "msg_4_assistant" - ? { - ...message, - time: { created: 4 }, - cost: 0, - tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, - } - : message, + currentPageMessages: () => ({ + items: messages + .map((message) => + !settled && message.type === "assistant" && message.id === "msg_4_assistant" + ? { + ...message, + time: { created: 4 }, + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + } + : message, + ) + .toReversed(), + throughSeq: 0, + }), + }) + await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) + await expectSessionTitle(page, "Context usage regression") + const usage = page.getByRole("button", { name: "View context usage", exact: true }).first() + const percentage = reverted ? 25 : 50 + const expectPercentage = (value: number) => + expect + .poll(async () => + usage.locator('circle[data-slot$="-progress"]').evaluate((circle) => { + const total = Number(circle.getAttribute("stroke-dasharray")) + return Math.round(100 * (1 - Number(circle.getAttribute("stroke-dashoffset")) / total)) + }), ) - .toReversed(), - throughSeq: 0, - }), - }) - await page.goto(`/${base64Encode(directory)}/session/${sessionID}`) - await expectSessionTitle(page, "Context usage regression") - const usage = page.getByRole("button", { name: "View context usage", exact: true }).first() - const percentage = reverted ? 25 : 50 - const expectPercentage = (value: number) => - expect - .poll(async () => - usage.locator('circle[data-slot$="-progress"]').evaluate((circle) => { - const total = Number(circle.getAttribute("stroke-dasharray")) - return Math.round(100 * (1 - Number(circle.getAttribute("stroke-dashoffset")) / total)) + .toBe(value) + await expectPercentage(mode === "live" ? 25 : percentage) + if (mode === "live") { + settled = true + await transport.send( + event("session.next.step.ended", { + sessionID, + timestamp: 10, + assistantMessageID: "msg_4_assistant", + finish: "stop", + cost: 1.25, + tokens: { input: 40_000, output: 10_000, reasoning: 0, cache: { read: 0, write: 0 } }, }), ) - .toBe(value) - await expectPercentage(mode === "live" ? 25 : percentage) - if (mode === "live") { - settled = true - await transport.send( - event("session.next.step.ended", { - sessionID, - timestamp: 10, - assistantMessageID: "msg_4_assistant", - finish: "stop", - cost: 1.25, - tokens: { input: 40_000, output: 10_000, reasoning: 0, cache: { read: 0, write: 0 } }, - }), + await expectPercentage(50) + } + await usage.hover() + await expect(page.getByRole("tooltip")).toContainText(`${percentage}%`) + await expect(page.getByRole("tooltip")).toContainText((percentage * 1_000).toLocaleString("en-US")) + await usage.click() + await expect(page.getByText("Total Tokens", { exact: true }).locator("..")).toContainText( + (percentage * 1_000).toLocaleString("en-US"), ) - await expectPercentage(50) - } - await usage.hover() - await expect(page.getByRole("tooltip")).toContainText(`${percentage}%`) - await expect(page.getByRole("tooltip")).toContainText((percentage * 1_000).toLocaleString("en-US")) - await usage.click() - await expect(page.getByText("Total Tokens", { exact: true }).locator("..")).toContainText( - (percentage * 1_000).toLocaleString("en-US"), - ) - await expect(page.getByText("Usage", { exact: true }).last().locator("..")).toContainText(`${percentage}%`) - }) + await expect(page.getByText("Usage", { exact: true }).last().locator("..")).toContainText(`${percentage}%`) + }) + } } diff --git a/packages/app/e2e/user-story/model-selection-flow.spec.ts b/packages/app/e2e/user-story/model-selection-flow.spec.ts index 4c1a4b49c9f2..10a78242d455 100644 --- a/packages/app/e2e/user-story/model-selection-flow.spec.ts +++ b/packages/app/e2e/user-story/model-selection-flow.spec.ts @@ -105,8 +105,8 @@ test("creates a session in a new project, connects OpenCode Go, and selects its await page.locator('[data-provider-id="opencode-go"]').click() await requested.promise expect(errors).toEqual([]) - catalogue.resolve() await page.locator('[data-input="provider-api-key"]').fill("mock-go-api-key") + catalogue.resolve() await page.locator('[data-action="provider-connect-submit"]').click() await expect(page.locator('[data-component="dialog-v2"]')).toHaveCount(0) expect(connections).toEqual([{ integrationID: "opencode-go", body: { key: "mock-go-api-key" } }]) diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index 4f970032b862..66cf2eec0f35 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -1,4 +1,4 @@ -import { Match, Show, Switch, createMemo, type ComponentProps, type JSX } from "solid-js" +import { Match, Show, Switch, batch, createMemo, type ComponentProps, type JSX } from "solid-js" import { ProgressCircle } from "@opencode-ai/ui/progress-circle" import { ProgressCircleV2 } from "@opencode-ai/ui/v2/progress-circle-v2" import { Button } from "@opencode-ai/ui/button" @@ -41,10 +41,12 @@ function openSessionContext(args: { layout: ReturnType tabs: ReturnType["tabs"]> }) { - args.view.reviewPanel.open(args.view.reviewPanel.opened() ? "other" : "context-button") - if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all") - void args.tabs.open("context") - args.tabs.setActive("context") + batch(() => { + args.view.reviewPanel.open(args.view.reviewPanel.opened() ? "other" : "context-button") + if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all") + void args.tabs.open("context") + args.tabs.setActive("context") + }) } export function SessionContextUsage(props: SessionContextUsageProps) { diff --git a/packages/app/src/hooks/use-providers.ts b/packages/app/src/hooks/use-providers.ts index 36c0407923d6..b87af5c27e44 100644 --- a/packages/app/src/hooks/use-providers.ts +++ b/packages/app/src/hooks/use-providers.ts @@ -50,7 +50,8 @@ export function useProviders(directory: Accessor) { } const all = createMemo(() => { - const value = choices.latest + // Optional connection choices must not suspend the session while a picker mounts. + const value = choices.state === "ready" || choices.state === "refreshing" ? choices.latest : undefined return mergeProviderChoices( providers().all, value?.server === serverSDK() && value.directory === dir() ? value.items : [], diff --git a/packages/app/src/utils/session-message.test.ts b/packages/app/src/utils/session-message.test.ts index 1e4aea53e18b..38c0e54b416d 100644 --- a/packages/app/src/utils/session-message.test.ts +++ b/packages/app/src/utils/session-message.test.ts @@ -96,6 +96,32 @@ describe("normalizeSessionMessages", () => { ]) }) + test("keeps admitted model metadata authoritative across a provider switch", () => { + const payload = { + version: 1, + agent: "plan", + model: { providerID: "anthropic", modelID: "sonnet", variant: "high" }, + parts: [{ type: "text", text: "Admitted prompt" }], + } + const result = normalizeCurrentSessionMessages("ses_1", [ + decodeCurrentMessage({ id: "user", type: "user", text: "Admitted prompt", payload, time: { created: 1 } }), + decodeCurrentMessage({ + id: "assistant", + type: "assistant", + agent: "build", + model: { providerID: "openai", id: "gpt" }, + content: [], + time: { created: 2, completed: 3 }, + }), + decodeCurrentMessage({ id: "pending", type: "user", text: "Next prompt", payload, time: { created: 4 } }), + ]) + expect(result.messages).toMatchObject([ + { id: "user", agent: payload.agent, model: payload.model }, + { id: "assistant", agent: "build", providerID: "openai", modelID: "gpt" }, + { id: "pending", agent: payload.agent, model: payload.model }, + ]) + }) + test("preserves current user attachments and payload comments in the compatibility projection", () => { const text = "Use @explore with @src/a.ts" const parts = [ @@ -138,6 +164,7 @@ describe("normalizeSessionMessages", () => { payload: { version: 1, agent: "build", model: { providerID: "provider", modelID: "model" }, parts }, }), ]) + expect(result.messages[0]).toMatchObject({ agent: "build", model: { providerID: "provider", modelID: "model" } }) expect(result.parts.get("msg_rich")).toMatchObject(parts) expect( result.parts.get("msg_rich")?.every((part) => part.sessionID === "ses_1" && part.messageID === "msg_rich"), diff --git a/packages/app/src/utils/session-message.ts b/packages/app/src/utils/session-message.ts index c3b1cd8905af..ad58e57f96d3 100644 --- a/packages/app/src/utils/session-message.ts +++ b/packages/app/src/utils/session-message.ts @@ -25,6 +25,8 @@ type PreparedAssistant = SessionMessageAssistant & { snapshot?: { diffs?: SnapshotFileDiff[] } } +type PreparedUser = SessionMessageUser & { payload?: Extract["payload"] } + const emptyTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } const emptyModel: { id: string; providerID: string; variant?: string } = { id: "", providerID: "" } const decodeToolInput = Schema.decodeUnknownOption(Schema.UnknownFromJsonString) @@ -145,6 +147,7 @@ export function normalizeSessionMessages(sessionID: string, source: readonly Ses let agent = "" let model = emptyModel let parentID: string | undefined + let parentHasPayload = false source.forEach((message) => { if (message.type === "agent-switched") { @@ -156,12 +159,19 @@ export function normalizeSessionMessages(sessionID: string, source: readonly Ses return } if (message.type === "user") { + const payload = (message as PreparedUser).payload + parentHasPayload = !!payload + if (payload) { + agent = payload.agent + model = { providerID: payload.model.providerID, id: payload.model.modelID, variant: payload.model.variant } + } parentID = message.id messages.push(userMessage(sessionID, message, agent, model)) parts.set(message.id, userParts(sessionID, message)) return } if (message.type === "synthetic" && message.description?.trim()) { + parentHasPayload = false parentID = message.id messages.push({ id: message.id, @@ -187,11 +197,13 @@ export function normalizeSessionMessages(sessionID: string, source: readonly Ses if (!parentID) return const parent = messages.findLast((item) => item.id === parentID) if (parent?.role === "user") { - parent.agent = message.agent - parent.model = { - providerID: message.model.providerID, - modelID: message.model.id, - variant: message.model.variant, + if (!parentHasPayload) { + parent.agent = message.agent + parent.model = { + providerID: message.model.providerID, + modelID: message.model.id, + variant: message.model.variant, + } } const diffs = (message as PreparedAssistant).snapshot?.diffs if (diffs) parent.summary = { diffs } @@ -298,10 +310,7 @@ function userMessage( } } -function userParts( - sessionID: string, - message: SessionMessageUser & { payload?: Extract["payload"] }, -): Part[] { +function userParts(sessionID: string, message: PreparedUser): Part[] { if (message.payload) { const ordinals = { text: 0, file: 0, agent: 0, subtask: 0 } return message.payload.parts.map((part) => ({ From 0aba3ce6a5766fd27716edb7cdfd195ffc04fb20 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 11:56:41 -0300 Subject: [PATCH 029/129] test(app): use schema-valid IDs in model projection regression --- packages/app/src/utils/session-message.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/app/src/utils/session-message.test.ts b/packages/app/src/utils/session-message.test.ts index 38c0e54b416d..e50a6de6f6ad 100644 --- a/packages/app/src/utils/session-message.test.ts +++ b/packages/app/src/utils/session-message.test.ts @@ -104,21 +104,21 @@ describe("normalizeSessionMessages", () => { parts: [{ type: "text", text: "Admitted prompt" }], } const result = normalizeCurrentSessionMessages("ses_1", [ - decodeCurrentMessage({ id: "user", type: "user", text: "Admitted prompt", payload, time: { created: 1 } }), + decodeCurrentMessage({ id: "msg_user", type: "user", text: "Admitted prompt", payload, time: { created: 1 } }), decodeCurrentMessage({ - id: "assistant", + id: "msg_assistant", type: "assistant", agent: "build", model: { providerID: "openai", id: "gpt" }, content: [], time: { created: 2, completed: 3 }, }), - decodeCurrentMessage({ id: "pending", type: "user", text: "Next prompt", payload, time: { created: 4 } }), + decodeCurrentMessage({ id: "msg_pending", type: "user", text: "Next prompt", payload, time: { created: 4 } }), ]) expect(result.messages).toMatchObject([ - { id: "user", agent: payload.agent, model: payload.model }, - { id: "assistant", agent: "build", providerID: "openai", modelID: "gpt" }, - { id: "pending", agent: payload.agent, model: payload.model }, + { id: "msg_user", agent: payload.agent, model: payload.model }, + { id: "msg_assistant", agent: "build", providerID: "openai", modelID: "gpt" }, + { id: "msg_pending", agent: payload.agent, model: payload.model }, ]) }) From 85c1f6722cd298960b3064c7c21ad7f6674068c0 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 12:30:30 -0300 Subject: [PATCH 030/129] fix(ci): expose test progress and budget Windows runner time --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c935fc964b1c..c1462f22c030 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -77,8 +77,8 @@ jobs: turbo-${{ runner.os }}- - name: Run unit tests - timeout-minutes: 20 - run: GITHUB_ACTIONS=false bun turbo test --concurrency=${{ github.repository == 'anomalyco/opencode' && '10' || '2' }} + timeout-minutes: ${{ github.repository != 'anomalyco/opencode' && runner.os == 'Windows' && 60 || 20 }} + run: GITHUB_ACTIONS=false bun turbo test --log-order=stream --concurrency=${{ github.repository == 'anomalyco/opencode' && '10' || '2' }} env: OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }} From 7f4687cb760e1f1a462f959978ee893694c62c1b Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 13:29:51 -0300 Subject: [PATCH 031/129] test(core): budget native shell and Git integration startup --- packages/core/test/session-prompt.test.ts | 53 +++--- packages/core/test/snapshot.test.ts | 188 +++++++++++----------- 2 files changed, 125 insertions(+), 116 deletions(-) diff --git a/packages/core/test/session-prompt.test.ts b/packages/core/test/session-prompt.test.ts index 976dc609f8e9..557d7774598f 100644 --- a/packages/core/test/session-prompt.test.ts +++ b/packages/core/test/session-prompt.test.ts @@ -177,31 +177,34 @@ describe("SessionV2.prompt", () => { }), ) - it.effect("records shell commands and output in current Session history", () => - Effect.gen(function* () { - yield* setup - const { db } = yield* Database.Service - yield* db - .update(SessionTable) - .set({ directory: process.cwd() }) - .where(eq(SessionTable.id, sessionID)) - .run() - .pipe(Effect.orDie) - const session = yield* SessionV2.Service - - const command = Shell.ps(Shell.preferred() ?? "") - ? "[Console]::Out.Write('current-shell-output')" - : "printf current-shell-output" - yield* session.shell({ sessionID, command }) - - expect(yield* session.messages({ sessionID, order: "asc" })).toMatchObject([ - { - type: "shell", - command, - output: "current-shell-output", - }, - ]) - }), + it.effect( + "records shell commands and output in current Session history", + () => + Effect.gen(function* () { + yield* setup + const { db } = yield* Database.Service + yield* db + .update(SessionTable) + .set({ directory: process.cwd() }) + .where(eq(SessionTable.id, sessionID)) + .run() + .pipe(Effect.orDie) + const session = yield* SessionV2.Service + + const command = Shell.ps(Shell.preferred() ?? "") + ? "[Console]::Out.Write('current-shell-output')" + : "printf current-shell-output" + yield* session.shell({ sessionID, command }) + + expect(yield* session.messages({ sessionID, order: "asc" })).toMatchObject([ + { + type: "shell", + command, + output: "current-shell-output", + }, + ]) + }), + 30_000, ) it.effect("pending UI inputs exclude queued, discarded and promoted messages", () => diff --git a/packages/core/test/snapshot.test.ts b/packages/core/test/snapshot.test.ts index 5e01fefc17dc..9ed3637392a1 100644 --- a/packages/core/test/snapshot.test.ts +++ b/packages/core/test/snapshot.test.ts @@ -13,58 +13,61 @@ import { tmpdir } from "./fixture/tmpdir" import { testEffect } from "./lib/effect" describe("Snapshot", () => { - testEffect(Layer.empty).live("captures and restores Location-scoped changes", () => - Effect.acquireUseRelease( - Effect.promise(() => tmpdir()), - (tmp) => - Effect.gen(function* () { - const project = path.join(tmp.path, "project") - const location = path.join(project, "scope") - yield* Effect.promise(async () => { - await fs.mkdir(location, { recursive: true }) - await fs.writeFile(path.join(location, "tracked.txt"), "one\n") - await fs.writeFile(path.join(project, "outside.txt"), "outside\n") - await $`git init`.cwd(project).quiet() - await $`git config core.fsmonitor false`.cwd(project).quiet() - await $`git config commit.gpgsign false`.cwd(project).quiet() - await $`git config user.email test@opencode.test`.cwd(project).quiet() - await $`git config user.name Test`.cwd(project).quiet() - await $`git add .`.cwd(project).quiet() - await $`git commit -m initial`.cwd(project).quiet() - }) - - const layer = snapshotLayer(tmp.path, location) - yield* Effect.gen(function* () { - const snapshot = yield* Snapshot.Service - const before = yield* snapshot.capture() - expect(before).toBeDefined() - if (!before) return - + testEffect(Layer.empty).live( + "captures and restores Location-scoped changes", + () => + Effect.acquireUseRelease( + Effect.promise(() => tmpdir()), + (tmp) => + Effect.gen(function* () { + const project = path.join(tmp.path, "project") + const location = path.join(project, "scope") yield* Effect.promise(async () => { - await fs.writeFile(path.join(location, "tracked.txt"), "two\n") - await fs.writeFile(path.join(location, "added.txt"), "added\n") - await fs.writeFile(path.join(project, "outside.txt"), "changed outside\n") + await fs.mkdir(location, { recursive: true }) + await fs.writeFile(path.join(location, "tracked.txt"), "one\n") + await fs.writeFile(path.join(project, "outside.txt"), "outside\n") + await $`git init`.cwd(project).quiet() + await $`git config core.fsmonitor false`.cwd(project).quiet() + await $`git config commit.gpgsign false`.cwd(project).quiet() + await $`git config user.email test@opencode.test`.cwd(project).quiet() + await $`git config user.name Test`.cwd(project).quiet() + await $`git add .`.cwd(project).quiet() + await $`git commit -m initial`.cwd(project).quiet() }) - const after = yield* snapshot.capture() - expect(after).toBeDefined() - if (!after) return - expect(yield* snapshot.files({ from: before, to: after })).toEqual([ - RelativePath.make("scope/added.txt"), - RelativePath.make("scope/tracked.txt"), - ]) - const plan = new Map([[RelativePath.make("scope/tracked.txt"), before]]) - const preview = yield* snapshot.preview({ files: plan, context: 1 }) - expect(preview).toHaveLength(1) - expect(preview[0]?.path).toBe(RelativePath.make("scope/tracked.txt")) - yield* snapshot.restore({ files: plan }) - expect(yield* read(path.join(location, "tracked.txt"))).toBe("one\n") - expect(yield* read(path.join(location, "added.txt"))).toBe("added\n") - expect(yield* read(path.join(project, "outside.txt"))).toBe("changed outside\n") - }).pipe(Effect.provide(layer)) - }), - (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), - ), + const layer = snapshotLayer(tmp.path, location) + yield* Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + const before = yield* snapshot.capture() + expect(before).toBeDefined() + if (!before) return + + yield* Effect.promise(async () => { + await fs.writeFile(path.join(location, "tracked.txt"), "two\n") + await fs.writeFile(path.join(location, "added.txt"), "added\n") + await fs.writeFile(path.join(project, "outside.txt"), "changed outside\n") + }) + const after = yield* snapshot.capture() + expect(after).toBeDefined() + if (!after) return + + expect(yield* snapshot.files({ from: before, to: after })).toEqual([ + RelativePath.make("scope/added.txt"), + RelativePath.make("scope/tracked.txt"), + ]) + const plan = new Map([[RelativePath.make("scope/tracked.txt"), before]]) + const preview = yield* snapshot.preview({ files: plan, context: 1 }) + expect(preview).toHaveLength(1) + expect(preview[0]?.path).toBe(RelativePath.make("scope/tracked.txt")) + yield* snapshot.restore({ files: plan }) + expect(yield* read(path.join(location, "tracked.txt"))).toBe("one\n") + expect(yield* read(path.join(location, "added.txt"))).toBe("added\n") + expect(yield* read(path.join(project, "outside.txt"))).toBe("changed outside\n") + }).pipe(Effect.provide(layer)) + }), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ), + 30_000, ) testEffect(Layer.empty).live("treats capture outside Git as unavailable", () => @@ -83,50 +86,53 @@ describe("Snapshot", () => { ), ) - testEffect(Layer.empty).live("isolates snapshot indexes by canonical Git worktree", () => - Effect.acquireUseRelease( - Effect.promise(() => tmpdir()), - (tmp) => - Effect.gen(function* () { - const project = path.join(tmp.path, "project") - const linked = path.join(tmp.path, "linked") - yield* Effect.promise(async () => { - await fs.mkdir(project) - await fs.writeFile(path.join(project, "tracked.txt"), "main\n") - await $`git init`.cwd(project).quiet() - await $`git config core.fsmonitor false`.cwd(project).quiet() - await $`git config commit.gpgsign false`.cwd(project).quiet() - await $`git config user.email test@opencode.test`.cwd(project).quiet() - await $`git config user.name Test`.cwd(project).quiet() - await $`git add .`.cwd(project).quiet() - await $`git commit -m initial`.cwd(project).quiet() - await $`git worktree add --detach ${linked} HEAD`.cwd(project).quiet() - }) + testEffect(Layer.empty).live( + "isolates snapshot indexes by canonical Git worktree", + () => + Effect.acquireUseRelease( + Effect.promise(() => tmpdir()), + (tmp) => + Effect.gen(function* () { + const project = path.join(tmp.path, "project") + const linked = path.join(tmp.path, "linked") + yield* Effect.promise(async () => { + await fs.mkdir(project) + await fs.writeFile(path.join(project, "tracked.txt"), "main\n") + await $`git init`.cwd(project).quiet() + await $`git config core.fsmonitor false`.cwd(project).quiet() + await $`git config commit.gpgsign false`.cwd(project).quiet() + await $`git config user.email test@opencode.test`.cwd(project).quiet() + await $`git config user.name Test`.cwd(project).quiet() + await $`git add .`.cwd(project).quiet() + await $`git commit -m initial`.cwd(project).quiet() + await $`git worktree add --detach ${linked} HEAD`.cwd(project).quiet() + }) - const capture = (directory: string) => - Effect.gen(function* () { - const snapshot = yield* Snapshot.Service - return yield* snapshot.capture() - }).pipe(Effect.provide(snapshotLayer(tmp.path, directory))) - expect(yield* capture(project)).toBeDefined() - expect(yield* capture(linked)).toBeDefined() + const capture = (directory: string) => + Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + return yield* snapshot.capture() + }).pipe(Effect.provide(snapshotLayer(tmp.path, directory))) + expect(yield* capture(project)).toBeDefined() + expect(yield* capture(linked)).toBeDefined() - const projectID = yield* Effect.gen(function* () { - return (yield* Location.Service).project.id - }).pipe( - Effect.provide( - AppNodeBuilder.build(Location.boundNode(Location.Ref.make({ directory: AbsolutePath.make(project) }))), - ), - ) - expect( - yield* Effect.promise(() => fs.stat(path.join(tmp.path, "snapshot", projectID, Hash.fast(project)))), - ).toBeDefined() - expect( - yield* Effect.promise(() => fs.stat(path.join(tmp.path, "snapshot", projectID, Hash.fast(linked)))), - ).toBeDefined() - }), - (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), - ), + const projectID = yield* Effect.gen(function* () { + return (yield* Location.Service).project.id + }).pipe( + Effect.provide( + AppNodeBuilder.build(Location.boundNode(Location.Ref.make({ directory: AbsolutePath.make(project) }))), + ), + ) + expect( + yield* Effect.promise(() => fs.stat(path.join(tmp.path, "snapshot", projectID, Hash.fast(project)))), + ).toBeDefined() + expect( + yield* Effect.promise(() => fs.stat(path.join(tmp.path, "snapshot", projectID, Hash.fast(linked)))), + ).toBeDefined() + }), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ), + 30_000, ) testEffect(Layer.empty).live("checks out a legacy revert snapshot without removing unrelated files", () => From 758e264d83d6b8578a344c1c4ca7628b5feabec4 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 14:11:11 -0300 Subject: [PATCH 032/129] test(opencode): make native smoke harnesses portable --- .../opencode/test/cli/acp/lifecycle.test.ts | 3 ++- .../cli/tui/prompt-queue-tui-smoke.cli.test.ts | 4 +++- .../test/tool/external-directory.test.ts | 5 +---- packages/opencode/test/tool/read.test.ts | 5 +---- .../cli/tui/scripts/prompt-queue-tui-smoke.sh | 17 ++++++++++------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/opencode/test/cli/acp/lifecycle.test.ts b/packages/opencode/test/cli/acp/lifecycle.test.ts index 9f2558ea2f58..58c7541b9420 100644 --- a/packages/opencode/test/cli/acp/lifecycle.test.ts +++ b/packages/opencode/test/cli/acp/lifecycle.test.ts @@ -18,7 +18,8 @@ describe("opencode acp lifecycle subprocess", () => { const acp = yield* opencode.acp() acp.close() - const code = yield* Effect.promise(() => acp.exited).pipe(Effect.timeout(Duration.seconds(5))) + // EOF is sent before startup, so this includes cold process/module loading. + const code = yield* Effect.promise(() => acp.exited).pipe(Effect.timeout(Duration.seconds(30))) expect(code).toBe(0) }), 60_000, diff --git a/packages/opencode/test/cli/tui/prompt-queue-tui-smoke.cli.test.ts b/packages/opencode/test/cli/tui/prompt-queue-tui-smoke.cli.test.ts index ef072fa9e83a..7e713f7c2b6f 100644 --- a/packages/opencode/test/cli/tui/prompt-queue-tui-smoke.cli.test.ts +++ b/packages/opencode/test/cli/tui/prompt-queue-tui-smoke.cli.test.ts @@ -34,7 +34,7 @@ const deferredAsPromise = (deferred: Deferred.Deferred): PromiseLike => }, }) -describe("prompt queue TUI smoke (serve + script)", () => { +describe.each([false, true])("prompt queue TUI smoke (skipAttach=%s)", (skipAttach) => { cliIt.live( "queues three deferred prompts in fifo via serve API and attach screencap", ({ llm, home, opencode }) => @@ -132,6 +132,7 @@ describe("prompt queue TUI smoke (serve + script)", () => { OPENCODE_ARTIFACT_DIR: artifactDir, OPENCODE_QUEUE_ONLY: "1", OPENCODE_EDIT_FIRST: "1", + OPENCODE_SKIP_ATTACH: skipAttach ? "1" : "0", OPENCODE_CLI_ENTRY: path.join(opencodeRoot, "src/index.ts"), }, stdout: "pipe", @@ -200,6 +201,7 @@ describe("prompt queue TUI smoke (serve + script)", () => { readFile(path.join(artifactDir, "tui-attach.txt"), "utf8").catch(() => "attach skipped"), ) expect(attach.length).toBeGreaterThan(0) + if (skipAttach) expect(attach).toContain("queue edit exercised through API, not terminal") }), 120_000, ) diff --git a/packages/opencode/test/tool/external-directory.test.ts b/packages/opencode/test/tool/external-directory.test.ts index d43accfb70dc..d50060fe300b 100644 --- a/packages/opencode/test/tool/external-directory.test.ts +++ b/packages/opencode/test/tool/external-directory.test.ts @@ -116,10 +116,7 @@ describe("tool.assertExternalDirectory", () => { yield* Effect.promise(() => Bun.write(path.join(outerTmp, "outside.txt"), "x")) const target = path.join(outerTmp, "outside.txt") - const alt = target - .replace(/^[A-Za-z]:/, "") - .replaceAll("\\", "/") - .toLowerCase() + const alt = target.replaceAll("\\", "/").toLowerCase() yield* assertExternalDirectoryEffect(ctx, alt) diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index c1ef61b227dd..89f482b8d79b 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -192,10 +192,7 @@ describe("tool.read external_directory permission", () => { const { items, next } = asks() const target = path.join(dir, "test.txt") - const alt = target - .replace(/^[A-Za-z]:/, "") - .replaceAll("\\", "/") - .toLowerCase() + const alt = target.replaceAll("\\", "/").toLowerCase() yield* exec(dir, { filePath: alt }, next) const read = items.find((item) => item.permission === "read") diff --git a/packages/tui/test/cli/tui/scripts/prompt-queue-tui-smoke.sh b/packages/tui/test/cli/tui/scripts/prompt-queue-tui-smoke.sh index 036c9deae103..17d057bd7a58 100755 --- a/packages/tui/test/cli/tui/scripts/prompt-queue-tui-smoke.sh +++ b/packages/tui/test/cli/tui/scripts/prompt-queue-tui-smoke.sh @@ -10,7 +10,7 @@ # OPENCODE_ARTIFACT_DIR — writes session-id.txt, queued.txt, tui-attach.txt # OPENCODE_QUEUE_ONLY=1 — only queue deferred messages (open turn started elsewhere) # OPENCODE_EDIT_FIRST=1 — edit the first queued message and save it with Return -# OPENCODE_SKIP_ATTACH=1 — skip `script` capture of `opencode attach` +# OPENCODE_SKIP_ATTACH=1 — skip terminal capture; edit through the API instead # OPENCODE_CLI_ENTRY — path to src/index.ts (defaults below) # set -euo pipefail @@ -78,12 +78,15 @@ attach_capture() { local sid=$1 local out="${ARTIFACT_DIR}/tui-attach.txt" : >"$out" - if [[ "${OPENCODE_SKIP_ATTACH:-}" == "1" ]]; then - printf 'attach skipped\n' >"$out" - return 0 - fi - if ! command -v script >/dev/null 2>&1; then - printf 'attach skipped: script(1) not found\n' >"$out" + if [[ "${OPENCODE_SKIP_ATTACH:-}" == "1" ]] || ! command -v script >/dev/null 2>&1; then + printf 'attach skipped: disabled or script(1) unavailable\n' >"$out" + if [[ "${OPENCODE_EDIT_FIRST:-}" == "1" ]]; then + local queued + queued="$(api GET "/api/session/${sid}/queue" | jq -ec '.data[0]')" + api PATCH "/api/session/${sid}/queue/$(jq -er '.id' <<<"$queued")" \ + -d "$(jq -c '{payload: (.payload | .parts[0].text += "-edited")}' <<<"$queued")" >/dev/null + printf 'queue edit exercised through API, not terminal\n' >>"$out" + fi return 0 fi if [[ "${OPENCODE_EDIT_FIRST:-}" == "1" ]]; then From ad00df82a1cdd4e617445ce34dc4c55ee375af27 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 14:45:38 -0300 Subject: [PATCH 033/129] test(core): align integration timeout with opencode suite --- packages/core/package.json | 2 +- packages/core/test/session-prompt.test.ts | 53 +++--- packages/core/test/snapshot.test.ts | 188 +++++++++++----------- 3 files changed, 117 insertions(+), 126 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index f411779db980..c50effce2b19 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -9,7 +9,7 @@ "db": "bun drizzle-kit", "migration": "bun run script/migration.ts", "fix-node-pty": "bun run script/fix-node-pty.ts", - "test": "bun test --only-failures", + "test": "bun test --timeout 30000 --only-failures", "typecheck": "tsgo --noEmit" }, "bin": { diff --git a/packages/core/test/session-prompt.test.ts b/packages/core/test/session-prompt.test.ts index 557d7774598f..976dc609f8e9 100644 --- a/packages/core/test/session-prompt.test.ts +++ b/packages/core/test/session-prompt.test.ts @@ -177,34 +177,31 @@ describe("SessionV2.prompt", () => { }), ) - it.effect( - "records shell commands and output in current Session history", - () => - Effect.gen(function* () { - yield* setup - const { db } = yield* Database.Service - yield* db - .update(SessionTable) - .set({ directory: process.cwd() }) - .where(eq(SessionTable.id, sessionID)) - .run() - .pipe(Effect.orDie) - const session = yield* SessionV2.Service - - const command = Shell.ps(Shell.preferred() ?? "") - ? "[Console]::Out.Write('current-shell-output')" - : "printf current-shell-output" - yield* session.shell({ sessionID, command }) - - expect(yield* session.messages({ sessionID, order: "asc" })).toMatchObject([ - { - type: "shell", - command, - output: "current-shell-output", - }, - ]) - }), - 30_000, + it.effect("records shell commands and output in current Session history", () => + Effect.gen(function* () { + yield* setup + const { db } = yield* Database.Service + yield* db + .update(SessionTable) + .set({ directory: process.cwd() }) + .where(eq(SessionTable.id, sessionID)) + .run() + .pipe(Effect.orDie) + const session = yield* SessionV2.Service + + const command = Shell.ps(Shell.preferred() ?? "") + ? "[Console]::Out.Write('current-shell-output')" + : "printf current-shell-output" + yield* session.shell({ sessionID, command }) + + expect(yield* session.messages({ sessionID, order: "asc" })).toMatchObject([ + { + type: "shell", + command, + output: "current-shell-output", + }, + ]) + }), ) it.effect("pending UI inputs exclude queued, discarded and promoted messages", () => diff --git a/packages/core/test/snapshot.test.ts b/packages/core/test/snapshot.test.ts index 9ed3637392a1..5e01fefc17dc 100644 --- a/packages/core/test/snapshot.test.ts +++ b/packages/core/test/snapshot.test.ts @@ -13,61 +13,58 @@ import { tmpdir } from "./fixture/tmpdir" import { testEffect } from "./lib/effect" describe("Snapshot", () => { - testEffect(Layer.empty).live( - "captures and restores Location-scoped changes", - () => - Effect.acquireUseRelease( - Effect.promise(() => tmpdir()), - (tmp) => - Effect.gen(function* () { - const project = path.join(tmp.path, "project") - const location = path.join(project, "scope") - yield* Effect.promise(async () => { - await fs.mkdir(location, { recursive: true }) - await fs.writeFile(path.join(location, "tracked.txt"), "one\n") - await fs.writeFile(path.join(project, "outside.txt"), "outside\n") - await $`git init`.cwd(project).quiet() - await $`git config core.fsmonitor false`.cwd(project).quiet() - await $`git config commit.gpgsign false`.cwd(project).quiet() - await $`git config user.email test@opencode.test`.cwd(project).quiet() - await $`git config user.name Test`.cwd(project).quiet() - await $`git add .`.cwd(project).quiet() - await $`git commit -m initial`.cwd(project).quiet() - }) + testEffect(Layer.empty).live("captures and restores Location-scoped changes", () => + Effect.acquireUseRelease( + Effect.promise(() => tmpdir()), + (tmp) => + Effect.gen(function* () { + const project = path.join(tmp.path, "project") + const location = path.join(project, "scope") + yield* Effect.promise(async () => { + await fs.mkdir(location, { recursive: true }) + await fs.writeFile(path.join(location, "tracked.txt"), "one\n") + await fs.writeFile(path.join(project, "outside.txt"), "outside\n") + await $`git init`.cwd(project).quiet() + await $`git config core.fsmonitor false`.cwd(project).quiet() + await $`git config commit.gpgsign false`.cwd(project).quiet() + await $`git config user.email test@opencode.test`.cwd(project).quiet() + await $`git config user.name Test`.cwd(project).quiet() + await $`git add .`.cwd(project).quiet() + await $`git commit -m initial`.cwd(project).quiet() + }) - const layer = snapshotLayer(tmp.path, location) - yield* Effect.gen(function* () { - const snapshot = yield* Snapshot.Service - const before = yield* snapshot.capture() - expect(before).toBeDefined() - if (!before) return + const layer = snapshotLayer(tmp.path, location) + yield* Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + const before = yield* snapshot.capture() + expect(before).toBeDefined() + if (!before) return - yield* Effect.promise(async () => { - await fs.writeFile(path.join(location, "tracked.txt"), "two\n") - await fs.writeFile(path.join(location, "added.txt"), "added\n") - await fs.writeFile(path.join(project, "outside.txt"), "changed outside\n") - }) - const after = yield* snapshot.capture() - expect(after).toBeDefined() - if (!after) return + yield* Effect.promise(async () => { + await fs.writeFile(path.join(location, "tracked.txt"), "two\n") + await fs.writeFile(path.join(location, "added.txt"), "added\n") + await fs.writeFile(path.join(project, "outside.txt"), "changed outside\n") + }) + const after = yield* snapshot.capture() + expect(after).toBeDefined() + if (!after) return - expect(yield* snapshot.files({ from: before, to: after })).toEqual([ - RelativePath.make("scope/added.txt"), - RelativePath.make("scope/tracked.txt"), - ]) - const plan = new Map([[RelativePath.make("scope/tracked.txt"), before]]) - const preview = yield* snapshot.preview({ files: plan, context: 1 }) - expect(preview).toHaveLength(1) - expect(preview[0]?.path).toBe(RelativePath.make("scope/tracked.txt")) - yield* snapshot.restore({ files: plan }) - expect(yield* read(path.join(location, "tracked.txt"))).toBe("one\n") - expect(yield* read(path.join(location, "added.txt"))).toBe("added\n") - expect(yield* read(path.join(project, "outside.txt"))).toBe("changed outside\n") - }).pipe(Effect.provide(layer)) - }), - (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), - ), - 30_000, + expect(yield* snapshot.files({ from: before, to: after })).toEqual([ + RelativePath.make("scope/added.txt"), + RelativePath.make("scope/tracked.txt"), + ]) + const plan = new Map([[RelativePath.make("scope/tracked.txt"), before]]) + const preview = yield* snapshot.preview({ files: plan, context: 1 }) + expect(preview).toHaveLength(1) + expect(preview[0]?.path).toBe(RelativePath.make("scope/tracked.txt")) + yield* snapshot.restore({ files: plan }) + expect(yield* read(path.join(location, "tracked.txt"))).toBe("one\n") + expect(yield* read(path.join(location, "added.txt"))).toBe("added\n") + expect(yield* read(path.join(project, "outside.txt"))).toBe("changed outside\n") + }).pipe(Effect.provide(layer)) + }), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ), ) testEffect(Layer.empty).live("treats capture outside Git as unavailable", () => @@ -86,53 +83,50 @@ describe("Snapshot", () => { ), ) - testEffect(Layer.empty).live( - "isolates snapshot indexes by canonical Git worktree", - () => - Effect.acquireUseRelease( - Effect.promise(() => tmpdir()), - (tmp) => - Effect.gen(function* () { - const project = path.join(tmp.path, "project") - const linked = path.join(tmp.path, "linked") - yield* Effect.promise(async () => { - await fs.mkdir(project) - await fs.writeFile(path.join(project, "tracked.txt"), "main\n") - await $`git init`.cwd(project).quiet() - await $`git config core.fsmonitor false`.cwd(project).quiet() - await $`git config commit.gpgsign false`.cwd(project).quiet() - await $`git config user.email test@opencode.test`.cwd(project).quiet() - await $`git config user.name Test`.cwd(project).quiet() - await $`git add .`.cwd(project).quiet() - await $`git commit -m initial`.cwd(project).quiet() - await $`git worktree add --detach ${linked} HEAD`.cwd(project).quiet() - }) + testEffect(Layer.empty).live("isolates snapshot indexes by canonical Git worktree", () => + Effect.acquireUseRelease( + Effect.promise(() => tmpdir()), + (tmp) => + Effect.gen(function* () { + const project = path.join(tmp.path, "project") + const linked = path.join(tmp.path, "linked") + yield* Effect.promise(async () => { + await fs.mkdir(project) + await fs.writeFile(path.join(project, "tracked.txt"), "main\n") + await $`git init`.cwd(project).quiet() + await $`git config core.fsmonitor false`.cwd(project).quiet() + await $`git config commit.gpgsign false`.cwd(project).quiet() + await $`git config user.email test@opencode.test`.cwd(project).quiet() + await $`git config user.name Test`.cwd(project).quiet() + await $`git add .`.cwd(project).quiet() + await $`git commit -m initial`.cwd(project).quiet() + await $`git worktree add --detach ${linked} HEAD`.cwd(project).quiet() + }) - const capture = (directory: string) => - Effect.gen(function* () { - const snapshot = yield* Snapshot.Service - return yield* snapshot.capture() - }).pipe(Effect.provide(snapshotLayer(tmp.path, directory))) - expect(yield* capture(project)).toBeDefined() - expect(yield* capture(linked)).toBeDefined() + const capture = (directory: string) => + Effect.gen(function* () { + const snapshot = yield* Snapshot.Service + return yield* snapshot.capture() + }).pipe(Effect.provide(snapshotLayer(tmp.path, directory))) + expect(yield* capture(project)).toBeDefined() + expect(yield* capture(linked)).toBeDefined() - const projectID = yield* Effect.gen(function* () { - return (yield* Location.Service).project.id - }).pipe( - Effect.provide( - AppNodeBuilder.build(Location.boundNode(Location.Ref.make({ directory: AbsolutePath.make(project) }))), - ), - ) - expect( - yield* Effect.promise(() => fs.stat(path.join(tmp.path, "snapshot", projectID, Hash.fast(project)))), - ).toBeDefined() - expect( - yield* Effect.promise(() => fs.stat(path.join(tmp.path, "snapshot", projectID, Hash.fast(linked)))), - ).toBeDefined() - }), - (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), - ), - 30_000, + const projectID = yield* Effect.gen(function* () { + return (yield* Location.Service).project.id + }).pipe( + Effect.provide( + AppNodeBuilder.build(Location.boundNode(Location.Ref.make({ directory: AbsolutePath.make(project) }))), + ), + ) + expect( + yield* Effect.promise(() => fs.stat(path.join(tmp.path, "snapshot", projectID, Hash.fast(project)))), + ).toBeDefined() + expect( + yield* Effect.promise(() => fs.stat(path.join(tmp.path, "snapshot", projectID, Hash.fast(linked)))), + ).toBeDefined() + }), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ), ) testEffect(Layer.empty).live("checks out a legacy revert snapshot without removing unrelated files", () => From 05da27eebf2f498079ad458bc02b0a54ec5c98d1 Mon Sep 17 00:00:00 2001 From: henry701 Date: Sat, 5 Sep 2026 16:28:36 -0300 Subject: [PATCH 034/129] test(ci): budget Windows native initialization without contention --- .github/workflows/test.yml | 2 +- packages/app/src/i18n/desktop-native.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c1462f22c030..910c1213751e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,7 +78,7 @@ jobs: - name: Run unit tests timeout-minutes: ${{ github.repository != 'anomalyco/opencode' && runner.os == 'Windows' && 60 || 20 }} - run: GITHUB_ACTIONS=false bun turbo test --log-order=stream --concurrency=${{ github.repository == 'anomalyco/opencode' && '10' || '2' }} + run: GITHUB_ACTIONS=false bun turbo test --log-order=stream --concurrency=${{ github.repository == 'anomalyco/opencode' && '10' || (runner.os == 'Windows' && '1' || '2') }} env: OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }} diff --git a/packages/app/src/i18n/desktop-native.test.ts b/packages/app/src/i18n/desktop-native.test.ts index 6fc8660cced5..17a81817a7ee 100644 --- a/packages/app/src/i18n/desktop-native.test.ts +++ b/packages/app/src/i18n/desktop-native.test.ts @@ -147,5 +147,5 @@ describe("desktop native ICU data", () => { expect(() => new Intl.DisplayNames(tag, { type: "language" }), `${locale} names`).not.toThrow() expect(() => new Intl.Segmenter(tag), `${locale} segmenter`).not.toThrow() } - }) + }, 30_000) }) From 7c2199d84a5830f70a8250731a42ff958145b4d6 Mon Sep 17 00:00:00 2001 From: far-ouq <125839498+far-ouq@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:29:29 -0400 Subject: [PATCH 035/129] fix(opencode): add GitLab reasoning variants (#47306) --- bun.lock | 6 +++--- packages/core/package.json | 2 +- packages/opencode/package.json | 2 +- packages/opencode/src/provider/transform.ts | 5 ++++- .../opencode/test/provider/transform.test.ts | 18 ++++++++++++++++-- 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/bun.lock b/bun.lock index 12e2a52f1b13..ac048b971514 100644 --- a/bun.lock +++ b/bun.lock @@ -341,7 +341,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.13.0", + "gitlab-ai-provider": "6.14.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", @@ -634,7 +634,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.13.0", + "gitlab-ai-provider": "6.14.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", @@ -3845,7 +3845,7 @@ "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], - "gitlab-ai-provider": ["gitlab-ai-provider@6.13.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-JDZhNjvoiB7xBfesNegXNDg6ItKf9M2l8/DifuIyuvYmGgaZnDDbl89QrOKSoIzAOpo/+3Om0qviBPm84nGEbg=="], + "gitlab-ai-provider": ["gitlab-ai-provider@6.14.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-qTkmFhcHXH0tjQN6i5D2lz9OpmYN0lwJdmFIXlsmy6IiYVV2dmsqMemlDNjpE5M8gdSC+61n4mByPzVsyBu0lg=="], "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], diff --git a/packages/core/package.json b/packages/core/package.json index 276b0fe8f76d..5e6ccd4c4738 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -108,7 +108,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.13.0", + "gitlab-ai-provider": "6.14.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index b5c8e1d3ac8e..4d3710b62cb5 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -120,7 +120,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.13.0", + "gitlab-ai-provider": "6.14.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 89b9c88ae3cc..f0be2d153751 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1824,11 +1824,14 @@ function reasoningEffort(model: Provider.Model, effort: string) { case "ai-gateway-provider": case "merge-gateway-ai-sdk-provider": return { reasoningEffort: effort } + case "gitlab-ai-provider": + if (model.family?.startsWith("gpt")) return { reasoningEffort: effort } + if (model.family?.startsWith("claude")) return { thinking: { type: "adaptive", effort } } + return case "@ai-sdk/cohere": case "@ai-sdk/perplexity": case "@ai-sdk/vercel": case "@ai-sdk/alibaba": - case "gitlab-ai-provider": return } } diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 9d767581b706..2f3e4a10c646 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -3798,10 +3798,11 @@ describe("ProviderTransform sampling defaults - DeepSeek", () => { describe("ProviderTransform.reasoningVariants", () => { const model = (reasoning_options: ModelsDev.Model["reasoning_options"]) => ({ reasoning_options }) as ModelsDev.Model - const target = (npm: string, id = "test-model") => + const target = (npm: string, id = "test-model", family = "") => ({ id, providerID: "test", + family, api: { id, npm, url: "" }, capabilities: { reasoning: true }, limit: { output: 64_000 }, @@ -3951,6 +3952,19 @@ describe("ProviderTransform.reasoningVariants", () => { }) }) + test("maps GitLab model efforts to provider-specific options", () => { + const options = model([{ type: "effort", values: ["max"] }]) + const openai = target("gitlab-ai-provider", "duo-chat-gpt-5-6-sol", "gpt-sol") + const anthropic = target("gitlab-ai-provider", "duo-chat-opus-4-8", "claude-opus") + + expect(ProviderTransform.reasoningVariants(options, openai)).toEqual({ + max: { reasoningEffort: "max" }, + }) + expect(ProviderTransform.reasoningVariants(options, anthropic)).toEqual({ + max: { thinking: { type: "adaptive", effort: "max" } }, + }) + }) + test("uses adaptive reasoning config for Anthropic models on Bedrock", () => { expect( ProviderTransform.reasoningVariants( @@ -4150,7 +4164,7 @@ describe("ProviderTransform.reasoningVariants", () => { expect(ProviderTransform.reasoningVariants(effort, target("@ai-sdk/github-copilot", "gemini-3-pro"))).toEqual({}) }) - test.each(["@ai-sdk/cohere", "@ai-sdk/perplexity", "@ai-sdk/vercel", "@ai-sdk/alibaba", "gitlab-ai-provider"])( + test.each(["@ai-sdk/cohere", "@ai-sdk/perplexity", "@ai-sdk/vercel", "@ai-sdk/alibaba"])( "does not invent effort controls for %s", (npm) => { expect(ProviderTransform.reasoningVariants(model([{ type: "effort", values: ["high"] }]), target(npm))).toEqual( From 1ddaeec480e64388276e4b76af141d4ce04f5b69 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 6 Sep 2026 12:31:27 +0800 Subject: [PATCH 036/129] docs(console): add localized refund FAQs for Go and Zen (#47575) --- packages/console/app/src/i18n/ar.ts | 7 +++++++ packages/console/app/src/i18n/br.ts | 7 +++++++ packages/console/app/src/i18n/da.ts | 7 +++++++ packages/console/app/src/i18n/de.ts | 7 +++++++ packages/console/app/src/i18n/en.ts | 6 ++++++ packages/console/app/src/i18n/es.ts | 7 +++++++ packages/console/app/src/i18n/fr.ts | 7 +++++++ packages/console/app/src/i18n/it.ts | 7 +++++++ packages/console/app/src/i18n/ja.ts | 7 +++++++ packages/console/app/src/i18n/ko.ts | 7 +++++++ packages/console/app/src/i18n/no.ts | 7 +++++++ packages/console/app/src/i18n/pl.ts | 7 +++++++ packages/console/app/src/i18n/ru.ts | 7 +++++++ packages/console/app/src/i18n/th.ts | 7 +++++++ packages/console/app/src/i18n/tr.ts | 7 +++++++ packages/console/app/src/i18n/uk.ts | 6 ++++++ packages/console/app/src/i18n/zh.ts | 7 +++++++ packages/console/app/src/i18n/zht.ts | 7 +++++++ packages/console/app/src/routes/go/index.tsx | 9 +++++++++ packages/console/app/src/routes/zen/index.tsx | 10 ++++++++++ 20 files changed, 143 insertions(+) diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index fb7dae2a844e..54547d776216 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "إلغاء", "common.creating": "جارٍ الإنشاء...", "common.create": "إنشاء", + "common.contactUs": "اتصل بنا", "common.videoUnsupported": "متصفحك لا يدعم وسم الفيديو.", "common.figure": "شكل {{n}}.", @@ -224,6 +225,9 @@ export const dict = { "zen.faq.q8": "هل يمكنني استخدام Zen مع وكلاء برمجة آخرين؟", "zen.faq.a8": "بينما يعمل Zen بشكل رائع مع OpenCode، يمكنك استخدام Zen مع أي وكيل. اتبع تعليمات الإعداد في وكيل البرمجة المفضل لديك.", + "zen.faq.q9": "هل يمكنني استرداد أموالي؟", + "zen.faq.a9": + "قد تكون مؤهلًا لاسترداد أموالك إذا تم الخصم خلال آخر 14 يومًا ولم تستخدم الرصيد الناتج عن عملية الشراء هذه. {{contact}} لطلب استرداد الأموال.", "zen.cta.start": "ابدأ مع Zen", "zen.pricing.title": "أضف رصيد 20 دولار (دفع حسب الاستخدام)", @@ -363,6 +367,9 @@ export const dict = { "go.faq.q9": "ما الفرق بين النماذج المجانية وGo؟", "go.faq.a9": "تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة قدرها 200 طلب/يوم. يقدّم Go مجموعة منسقة من النماذج مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، وأسبوعية، وشهرية)، تعادل الحصص الأساسية فيها تقريبًا $12 لكل 5 ساعات و$30 في الأسبوع و$60 في الشهر؛ وقد تختلف الحصص حسب النموذج (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).", + "go.faq.q10": "هل يمكنني استرداد أموالي؟", + "go.faq.a10": + "قد تكون مؤهلًا لاسترداد أموالك إذا تم الخصم خلال آخر 14 يومًا ولم تستخدم مخصصات Go خلال فترة الفوترة تلك. {{contact}} لطلب استرداد الأموال.", "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 7faba59026e3..a0ead2369689 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "Cancelar", "common.creating": "Criando...", "common.create": "Criar", + "common.contactUs": "Contate-nos", "common.videoUnsupported": "Seu navegador não suporta a tag de vídeo.", "common.figure": "Fig {{n}}.", @@ -228,6 +229,9 @@ export const dict = { "zen.faq.q8": "Posso usar o Zen com outros agentes de codificação?", "zen.faq.a8": "Embora o Zen funcione muito bem com o OpenCode, você pode usar o Zen com qualquer agente. Siga as instruções de configuração no seu agente de codificação preferido.", + "zen.faq.q9": "Posso receber um reembolso?", + "zen.faq.a9": + "Você pode ter direito a um reembolso se a cobrança foi feita nos últimos 14 dias e você não usou os créditos dessa compra. {{contact}} para solicitar um reembolso.", "zen.cta.start": "Comece com o Zen", "zen.pricing.title": "Adicionar $20 de saldo pré-pago", @@ -373,6 +377,9 @@ 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 oferece uma seleção de modelos com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a cotas básicas de $12 por 5 horas, $30 por semana e $60 por mês; as cotas específicas podem variar por modelo (as contagens reais de requisições variam de acordo com o modelo e o uso).", + "go.faq.q10": "Posso receber um reembolso?", + "go.faq.a10": + "Você pode ter direito a um reembolso se a cobrança foi feita nos últimos 14 dias e você não usou sua cota do Go durante esse período de faturamento. {{contact}} para solicitar um reembolso.", "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 a62039593876..3f659a2181ca 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "Annuller", "common.creating": "Opretter...", "common.create": "Opret", + "common.contactUs": "Kontakt os", "common.videoUnsupported": "Din browser understøtter ikke video-tagget.", "common.figure": "Fig {{n}}.", @@ -226,6 +227,9 @@ export const dict = { "zen.faq.q8": "Kan jeg bruge Zen med andre kodningsagenter?", "zen.faq.a8": "Selvom Zen fungerer godt med OpenCode, kan du bruge Zen med enhver agent. Følg opsætningsinstruktionerne i din foretrukne kodningsagent.", + "zen.faq.q9": "Kan jeg få en refusion?", + "zen.faq.a9": + "Du kan muligvis få en refusion, hvis opkrævningen blev foretaget inden for de seneste 14 dage, og du ikke har brugt kreditten fra dette køb. {{contact}} for at anmode om en refusion.", "zen.cta.start": "Kom godt i gang med Zen", "zen.pricing.title": "Tilføj $20 Pay as you go-saldo", @@ -369,6 +373,9 @@ export const dict = { "go.faq.q9": "Hvad er forskellen på gratis modeller og Go?", "go.faq.a9": "Gratis modeller inkluderer Big Pickle plus kampagnemodeller, der er tilgængelige på det pågældende tidspunkt, med en kvote på 200 forespørgsler/dag. Go tilbyder et kurateret modeludvalg med højere forespørgselskvoter håndhævet over rullende perioder (5 timer, ugentligt og månedligt), omtrent svarende til basiskvoter på $12 pr. 5 timer, $30 pr. uge og $60 pr. måned; modelspecifikke kvoter kan variere (det faktiske antal forespørgsler varierer efter model og brug).", + "go.faq.q10": "Kan jeg få en refusion?", + "go.faq.a10": + "Du kan muligvis få en refusion, hvis opkrævningen blev foretaget inden for de seneste 14 dage, og du ikke har brugt din Go-kvote i den pågældende faktureringsperiode. {{contact}} for at anmode om en refusion.", "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 5dab86ea585d..dbb97d89a88d 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "Abbrechen", "common.creating": "Erstelle...", "common.create": "Erstellen", + "common.contactUs": "Kontaktiere uns", "common.videoUnsupported": "Dein Browser unterstützt das Video-Tag nicht.", "common.figure": "Abb. {{n}}.", @@ -228,6 +229,9 @@ export const dict = { "zen.faq.q8": "Kann ich Zen mit anderen Coding-Agents nutzen?", "zen.faq.a8": "Während Zen großartig mit OpenCode funktioniert, kannst du Zen mit jedem Agent nutzen. Folge den Einrichtungsanweisungen in deinem bevorzugten Coding-Agent.", + "zen.faq.q9": "Kann ich eine Rückerstattung erhalten?", + "zen.faq.a9": + "Du hast möglicherweise Anspruch auf eine Rückerstattung, wenn die Zahlung innerhalb der letzten 14 Tage erfolgt ist und du das Guthaben aus diesem Kauf nicht verwendet hast. {{contact}}, um eine Rückerstattung anzufordern.", "zen.cta.start": "Starte mit Zen", "zen.pricing.title": "Füge $20 Pay-as-you-go Guthaben hinzu", @@ -371,6 +375,9 @@ 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 bietet eine kuratierte Modellauswahl mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu Basiskontingenten von $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat; modellspezifische Kontingente können abweichen (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).", + "go.faq.q10": "Kann ich eine Rückerstattung erhalten?", + "go.faq.a10": + "Du hast möglicherweise Anspruch auf eine Rückerstattung, wenn die Zahlung innerhalb der letzten 14 Tage erfolgt ist und du dein Go-Kontingent in diesem Abrechnungszeitraum nicht genutzt hast. {{contact}}, um eine Rückerstattung anzufordern.", "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 ee4dfe1aaf43..e39b42eb761f 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -225,6 +225,9 @@ export const dict = { "zen.faq.q8": "Can I use Zen with other coding agents?", "zen.faq.a8": "While Zen works great with OpenCode, you can use Zen with any agent. Follow the setup instructions in your preferred coding agent.", + "zen.faq.q9": "Can I get a refund?", + "zen.faq.a9": + "You may qualify for a refund if the charge was made within the last 14 days and you have not used the credits from that purchase. {{contact}} to request a refund.", "zen.cta.start": "Get started with Zen", "zen.pricing.title": "Add $20 Pay as you go balance", @@ -368,6 +371,9 @@ 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 offers a curated model lineup with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to base allowances of $12 per 5 hours, $30 per week, and $60 per month; model-specific allowances may differ (actual request counts vary by model and usage).", + "go.faq.q10": "Can I get a refund?", + "go.faq.a10": + "You may qualify for a refund if the charge was made within the last 14 days and you have not used your Go allowance during that billing period. {{contact}} to request a refund.", "zen.api.error.rateLimitExceeded": "Rate limit exceeded. Please try again later.", "zen.api.error.modelNotSupported": "Model {{model}} is not supported", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index f9c7bbbd52eb..47fc8a446139 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "Cancelar", "common.creating": "Creando...", "common.create": "Crear", + "common.contactUs": "Contáctanos", "common.videoUnsupported": "Tu navegador no soporta la etiqueta de video.", "common.figure": "Fig {{n}}.", @@ -229,6 +230,9 @@ export const dict = { "zen.faq.q8": "¿Puedo usar Zen con otros agentes de codificación?", "zen.faq.a8": "Aunque Zen funciona genial con OpenCode, puedes usar Zen con cualquier agente. Sigue las instrucciones de configuración en tu agente de codificación preferido.", + "zen.faq.q9": "¿Puedo obtener un reembolso?", + "zen.faq.a9": + "Podrías tener derecho a un reembolso si el cargo se realizó en los últimos 14 días y no has utilizado el crédito de esa compra. {{contact}} para solicitar un reembolso.", "zen.cta.start": "Empieza con Zen", "zen.pricing.title": "Añade $20 de saldo prepago", @@ -374,6 +378,9 @@ 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 y los modelos promocionales disponibles en ese momento, con una cuota de 200 solicitudes/día. Go ofrece una selección de modelos con cuotas de solicitudes más altas aplicadas en ventanas móviles (de 5 horas, semanales y mensuales), aproximadamente equivalentes a cuotas base de 12 $ por 5 horas, 30 $ por semana y 60 $ por mes; las cuotas específicas pueden variar según el modelo (la cantidad real de solicitudes varía según el modelo y el uso).", + "go.faq.q10": "¿Puedo obtener un reembolso?", + "go.faq.a10": + "Podrías tener derecho a un reembolso si el cargo se realizó en los últimos 14 días y no has utilizado tu cuota de Go durante ese periodo de facturación. {{contact}} para solicitar un reembolso.", "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 ea7fccc63dc0..e5a913315177 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -57,6 +57,7 @@ export const dict = { "common.cancel": "Annuler", "common.creating": "Création...", "common.create": "Créer", + "common.contactUs": "Contactez-nous", "common.videoUnsupported": "Votre navigateur ne prend pas en charge la balise vidéo.", "common.figure": "Fig {{n}}.", @@ -227,6 +228,9 @@ export const dict = { "zen.faq.q8": "Puis-je utiliser Zen avec d'autres agents de code ?", "zen.faq.a8": "Zen fonctionne très bien avec OpenCode, mais vous pouvez utiliser Zen avec n'importe quel agent. Suivez les instructions de configuration dans votre agent préféré.", + "zen.faq.q9": "Puis-je obtenir un remboursement ?", + "zen.faq.a9": + "Vous pourriez avoir droit à un remboursement si le paiement a été effectué au cours des 14 derniers jours et que vous n’avez pas utilisé le crédit de cet achat. {{contact}} pour demander un remboursement.", "zen.cta.start": "Commencez avec Zen", "zen.pricing.title": "Ajoutez 20 $ de solde Pay as you go", @@ -374,6 +378,9 @@ export const dict = { "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 les modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go propose une sélection de modèles avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalents à des quotas de base de 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois ; les quotas propres à chaque modèle peuvent varier (le nombre réel de requêtes varie selon le modèle et l'utilisation).", + "go.faq.q10": "Puis-je obtenir un remboursement ?", + "go.faq.a10": + "Vous pourriez avoir droit à un remboursement si le paiement a été effectué au cours des 14 derniers jours et que vous n’avez pas utilisé votre quota Go pendant cette période de facturation. {{contact}} pour demander un remboursement.", "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 fe44d02e022e..9b4d200043e5 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "Annulla", "common.creating": "Creazione...", "common.create": "Crea", + "common.contactUs": "Contattaci", "common.videoUnsupported": "Il tuo browser non supporta il tag video.", "common.figure": "Fig {{n}}.", @@ -226,6 +227,9 @@ export const dict = { "zen.faq.q8": "Posso usare Zen con altri agenti di coding?", "zen.faq.a8": "Anche se Zen funziona alla grande con OpenCode, puoi usare Zen con qualsiasi agente. Segui le istruzioni di configurazione nel tuo agente di coding preferito.", + "zen.faq.q9": "Posso ottenere un rimborso?", + "zen.faq.a9": + "Potresti avere diritto a un rimborso se l'addebito è stato effettuato negli ultimi 14 giorni e non hai utilizzato il credito di quell'acquisto. {{contact}} per richiedere un rimborso.", "zen.cta.start": "Inizia con Zen", "zen.pricing.title": "Aggiungi $20 di saldo a consumo", @@ -370,6 +374,9 @@ 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ù i modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go offre una selezione curata di modelli con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a quote base di $12 ogni 5 ore, $30 a settimana e $60 al mese; le quote specifiche possono variare in base al modello (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).", + "go.faq.q10": "Posso ottenere un rimborso?", + "go.faq.a10": + "Potresti avere diritto a un rimborso se l'addebito è stato effettuato negli ultimi 14 giorni e non hai utilizzato la tua quota Go durante quel periodo di fatturazione. {{contact}} per richiedere un rimborso.", "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 a79bdd12ecdd..de390eb6df34 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "キャンセル", "common.creating": "作成中...", "common.create": "作成", + "common.contactUs": "お問い合わせ", "common.videoUnsupported": "お使いのブラウザは video タグをサポートしていません。", "common.figure": "図 {{n}}.", @@ -224,6 +225,9 @@ export const dict = { "zen.faq.q8": "他のコーディングエージェントでもZenを使えますか?", "zen.faq.a8": "ZenはOpenCodeとの相性が良いですが、どのエージェントでもZenを利用できます。お使いのコーディングエージェントのセットアップ手順に従ってください。", + "zen.faq.q9": "返金を受けられますか?", + "zen.faq.a9": + "請求から14日以内で、その購入分のクレジットを一切使用していない場合、返金の対象となる可能性があります。返金を希望する場合は、{{contact}}ください。", "zen.cta.start": "Zenをはじめる", "zen.pricing.title": "$20の従量課金制残高を追加", @@ -368,6 +372,9 @@ export const dict = { "go.faq.q9": "無料モデルとGoの違いは何ですか?", "go.faq.a9": "無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。Goでは厳選されたモデルラインナップを利用でき、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。基本利用枠では概算で5時間あたり$12、週間$30、月間$60相当ですが、モデル別の利用枠は異なる場合があります(実際のリクエスト数はモデルと使用状況により異なります)。", + "go.faq.q10": "返金を受けられますか?", + "go.faq.a10": + "請求から14日以内で、その請求期間中にGoの利用枠を一切使用していない場合、返金の対象となる可能性があります。返金を希望する場合は、{{contact}}ください。", "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 72470d0330b8..ea4a0a5da717 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "취소", "common.creating": "생성 중...", "common.create": "만들기", + "common.contactUs": "문의하기", "common.videoUnsupported": "브라우저가 비디오 태그를 지원하지 않습니다.", "common.figure": "그림 {{n}}.", @@ -222,6 +223,9 @@ export const dict = { "zen.faq.q8": "다른 코딩 에이전트와 Zen을 사용할 수 있나요?", "zen.faq.a8": "Zen은 OpenCode와 훌륭하게 작동하지만, 어떤 에이전트와도 Zen을 사용할 수 있습니다. 선호하는 코딩 에이전트의 설정 지침을 따르세요.", + "zen.faq.q9": "환불받을 수 있나요?", + "zen.faq.a9": + "결제일로부터 14일 이내이며 해당 구매로 받은 크레딧을 전혀 사용하지 않은 경우 환불 대상이 될 수 있습니다. 환불을 요청하려면 {{contact}}를 선택해 주세요.", "zen.cta.start": "Zen 시작하기", "zen.pricing.title": "$20 선불 잔액 추가", @@ -362,6 +366,9 @@ export const dict = { "go.faq.q9": "무료 모델과 Go의 차이점은 무엇인가요?", "go.faq.a9": "무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 엄선된 모델 라인업을 제공하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 기본 할당량은 대략 5시간당 $12, 주당 $30, 월 $60에 해당하며 모델별 할당량은 다를 수 있습니다(실제 요청 수는 모델 및 사용량에 따라 다름).", + "go.faq.q10": "환불받을 수 있나요?", + "go.faq.a10": + "결제일로부터 14일 이내이며 해당 결제 기간에 Go 사용 한도를 전혀 사용하지 않은 경우 환불 대상이 될 수 있습니다. 환불을 요청하려면 {{contact}}를 선택해 주세요.", "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 2357766d2504..606f613dcd11 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "Avbryt", "common.creating": "Oppretter...", "common.create": "Opprett", + "common.contactUs": "Kontakt oss", "common.videoUnsupported": "Nettleseren din støtter ikke video-taggen.", "common.figure": "Fig {{n}}.", @@ -226,6 +227,9 @@ export const dict = { "zen.faq.q8": "Kan jeg bruke Zen med andre kodeagenter?", "zen.faq.a8": "Selv om Zen fungerer veldig bra med OpenCode, kan du bruke Zen med hvilken som helst agent. Følg oppsettinstruksjonene i din foretrukne kodeagent.", + "zen.faq.q9": "Kan jeg få refusjon?", + "zen.faq.a9": + "Du kan ha rett på refusjon hvis belastningen ble gjort i løpet av de siste 14 dagene og du ikke har brukt noe av kreditten fra kjøpet. {{contact}} for å be om refusjon.", "zen.cta.start": "Kom i gang med Zen", "zen.pricing.title": "Legg til $20 Pay as you go-saldo", @@ -370,6 +374,9 @@ export const dict = { "go.faq.q9": "Hva er forskjellen mellom gratis modeller og Go?", "go.faq.a9": "Gratis modeller inkluderer Big Pickle pluss kampanjemodeller som er tilgjengelige på det tidspunktet, med en kvote på 200 forespørsler/dag. Go tilbyr et kuratert modellutvalg med høyere forespørselskvoter som håndheves over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende basiskvoter på $12 per 5 timer, $30 per uke og $60 per måned; modellspesifikke kvoter kan variere (faktiske forespørselsantall varierer etter modell og bruk).", + "go.faq.q10": "Kan jeg få refusjon?", + "go.faq.a10": + "Du kan ha rett på refusjon hvis belastningen ble gjort i løpet av de siste 14 dagene og du ikke har brukt noe av Go-kvoten i den faktureringsperioden. {{contact}} for å be om refusjon.", "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 e2bb3d81167d..30614954c052 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -57,6 +57,7 @@ export const dict = { "common.cancel": "Anuluj", "common.creating": "Tworzenie...", "common.create": "Utwórz", + "common.contactUs": "Skontaktuj się z nami", "common.videoUnsupported": "Twoja przeglądarka nie obsługuje znacznika wideo.", "common.figure": "Rys. {{n}}.", @@ -227,6 +228,9 @@ export const dict = { "zen.faq.q8": "Czy mogę używać Zen z innymi agentami kodującymi?", "zen.faq.a8": "Chociaż Zen świetnie działa z OpenCode, możesz używać Zen z dowolnym agentem. Postępuj zgodnie z instrukcjami konfiguracji w swoim preferowanym agencie.", + "zen.faq.q9": "Czy mogę otrzymać zwrot pieniędzy?", + "zen.faq.a9": + "Możesz kwalifikować się do zwrotu, jeśli opłata została pobrana w ciągu ostatnich 14 dni i nie wykorzystano żadnych środków z tego zakupu. {{contact}}, aby poprosić o zwrot.", "zen.cta.start": "Zacznij korzystać z Zen", "zen.pricing.title": "Dodaj 20$ salda Pay as you go", @@ -371,6 +375,9 @@ 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 oferuje starannie dobrany zestaw modeli z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), odpowiadającymi w przybliżeniu bazowym limitom $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie; limity mogą się różnić zależnie od modelu (rzeczywista liczba zapytań zależy od modelu i użycia).", + "go.faq.q10": "Czy mogę otrzymać zwrot pieniędzy?", + "go.faq.a10": + "Możesz kwalifikować się do zwrotu, jeśli opłata została pobrana w ciągu ostatnich 14 dni i nie wykorzystano żadnej części limitu Go w tym okresie rozliczeniowym. {{contact}}, aby poprosić o zwrot.", "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 9e05c954916c..040c84c635e1 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "Отмена", "common.creating": "Создание...", "common.create": "Создать", + "common.contactUs": "Свяжитесь с нами", "common.videoUnsupported": "Ваш браузер не поддерживает видео тег.", "common.figure": "Рис {{n}}.", @@ -230,6 +231,9 @@ export const dict = { "zen.faq.q8": "Могу ли я использовать Zen с другими кодинг-агентами?", "zen.faq.a8": "Хотя Zen отлично работает с OpenCode, вы можете использовать Zen с любым агентом. Следуйте инструкциям по настройке в вашем любимом агенте.", + "zen.faq.q9": "Могу ли я получить возврат средств?", + "zen.faq.a9": + "Вы можете претендовать на возврат, если списание произошло в течение последних 14 дней и средства, полученные при этой покупке, не были использованы. {{contact}}, чтобы запросить возврат.", "zen.cta.start": "Начать работу с Zen", "zen.pricing.title": "Пополнить баланс на $20 (Pay as you go)", @@ -376,6 +380,9 @@ export const dict = { "go.faq.q9": "В чем разница между бесплатными моделями и Go?", "go.faq.a9": "Бесплатные модели включают Big Pickle и доступные на данный момент промо-модели с квотой 200 запросов/день. Go предлагает набор отобранных моделей с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно базовым лимитам $12 за 5 часов, $30 в неделю и $60 в месяц; лимиты для отдельных моделей могут отличаться (фактическое количество запросов зависит от модели и использования).", + "go.faq.q10": "Могу ли я получить возврат средств?", + "go.faq.a10": + "Вы можете претендовать на возврат, если списание произошло в течение последних 14 дней и вы не использовали лимит Go в этом расчетном периоде. {{contact}}, чтобы запросить возврат.", "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 56a45ba7da7d..16219983c969 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "ยกเลิก", "common.creating": "กำลังสร้าง...", "common.create": "สร้าง", + "common.contactUs": "ติดต่อเรา", "common.videoUnsupported": "เบราว์เซอร์ของคุณไม่รองรับแท็ก video", "common.figure": "รูปที่ {{n}}", @@ -225,6 +226,9 @@ export const dict = { "zen.faq.q8": "ฉันสามารถใช้ Zen กับเอเจนต์เขียนโค้ดอื่นได้หรือไม่?", "zen.faq.a8": "แม้ว่า Zen จะทำงานได้ดีเยี่ยมกับ OpenCode แต่คุณสามารถใช้ Zen กับเอเจนต์ใดก็ได้ เพียงทำตามคำแนะนำการตั้งค่าในเอเจนต์เขียนโค้ดที่คุณต้องการ", + "zen.faq.q9": "ฉันขอเงินคืนได้หรือไม่?", + "zen.faq.a9": + "หากมีการเรียกเก็บเงินภายใน 14 วันที่ผ่านมาและคุณยังไม่ได้ใช้เครดิตใดๆ จากการซื้อนั้น คุณอาจมีสิทธิ์ได้รับเงินคืน {{contact}}เพื่อขอเงินคืน", "zen.cta.start": "เริ่มต้นใช้งาน Zen", "zen.pricing.title": "เติมเงิน $20 แบบ Pay as you go", @@ -367,6 +371,9 @@ export const dict = { "go.faq.q9": "ความแตกต่างระหว่างโมเดลฟรีและ Go คืออะไร?", "go.faq.a9": "โมเดลฟรีประกอบด้วย Big Pickle และโมเดลโปรโมชันที่มีให้บริการในขณะนั้น โดยมีโควตา 200 คำขอ/วัน Go นำเสนอชุดโมเดลที่คัดสรร พร้อมโควตาคำขอที่สูงกว่าซึ่งบังคับใช้ตามกรอบเวลาแบบต่อเนื่อง (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าโควตาพื้นฐานประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน โดยโควตาเฉพาะอาจแตกต่างกันไปตามโมเดล (จำนวนคำขอจริงแตกต่างกันไปตามโมเดลและการใช้งาน)", + "go.faq.q10": "ฉันขอเงินคืนได้หรือไม่?", + "go.faq.a10": + "หากมีการเรียกเก็บเงินภายใน 14 วันที่ผ่านมาและคุณยังไม่ได้ใช้สิทธิ์การใช้งาน Go เลยในรอบการเรียกเก็บเงินนั้น คุณอาจมีสิทธิ์ได้รับเงินคืน {{contact}}เพื่อขอเงินคืน", "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 09014d0b6ae8..b43320093ec5 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -58,6 +58,7 @@ export const dict = { "common.cancel": "İptal", "common.creating": "Oluşturuluyor...", "common.create": "Oluştur", + "common.contactUs": "Bize ulaşın", "common.videoUnsupported": "Tarayıcınız video etiketini desteklemiyor.", "common.figure": "Şekil {{n}}.", @@ -227,6 +228,9 @@ export const dict = { "zen.faq.q8": "Zen'i diğer kodlama ajanlarıyla kullanabilir miyim?", "zen.faq.a8": "Zen OpenCode ile harika çalışır, ama Zen'i herhangi bir ajan ile kullanabilirsiniz. Tercih ettiğiniz kodlama ajanında kurulum talimatlarını izleyin.", + "zen.faq.q9": "Para iadesi alabilir miyim?", + "zen.faq.a9": + "Ücret son 14 gün içinde tahsil edildiyse ve bu satın alımdan gelen kredilerin hiçbirini kullanmadıysanız para iadesine hak kazanabilirsiniz. {{contact}} ve para iadesi talep edin.", "zen.cta.start": "Zen'i kullanmaya başlayın", "zen.pricing.title": "20$ Kullandıkça öde bakiyesi ekle", @@ -373,6 +377,9 @@ 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ıyla Big Pickle'ı ve o sırada mevcut olan promosyonel modelleri içerir. Go ise kayan zaman aralıklarında (5 saatlik, haftalık ve aylık) uygulanan daha yüksek istek kotalarıyla özenle seçilmiş model seçenekleri sunar. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerindeki temel kullanım haklarına eşdeğerdir; modele özgü kullanım hakları farklılık gösterebilir (gerçek istek sayıları modele ve kullanıma göre değişir).", + "go.faq.q10": "Para iadesi alabilir miyim?", + "go.faq.a10": + "Ücret son 14 gün içinde tahsil edildiyse ve ilgili faturalandırma döneminde Go kullanım hakkınızı hiç kullanmadıysanız para iadesine hak kazanabilirsiniz. {{contact}} ve para iadesi talep edin.", "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/uk.ts b/packages/console/app/src/i18n/uk.ts index ac613a7d974e..8b2aafc7ab98 100644 --- a/packages/console/app/src/i18n/uk.ts +++ b/packages/console/app/src/i18n/uk.ts @@ -226,6 +226,9 @@ export const dict = { "zen.faq.q8": "Чи можна використовувати Zen з іншими агентами кодування?", "zen.faq.a8": "Хоча Zen чудово працює з OpenCode, ви можете використовувати Zen з будь-яким агентом. Дотримуйтесь інструкцій з налаштування у вашому агенті.", + "zen.faq.q9": "Чи можу я отримати повернення коштів?", + "zen.faq.a9": + "Ви можете претендувати на повернення, якщо кошти було списано протягом останніх 14 днів і ви не використали кошти, отримані внаслідок цієї покупки. {{contact}}, щоб подати запит на повернення.", "zen.cta.start": "Почати з Zen", "zen.pricing.title": "Додати $20 балансу Pay as you go", @@ -369,6 +372,9 @@ export const dict = { "go.faq.q9": "Яка різниця між безкоштовними моделями та Go?", "go.faq.a9": "Безкоштовні моделі включають Big Pickle та доступні на той момент акційні моделі з квотою 200 запитів/день. Go пропонує добірку моделей із вищими квотами запитів, що застосовуються протягом ковзних періодів (5 годин, тижня та місяця), приблизно еквівалентними базовим лімітам $12 за 5 годин, $30 на тиждень і $60 на місяць; ліміти для окремих моделей можуть відрізнятися (фактична кількість запитів залежить від моделі та використання).", + "go.faq.q10": "Чи можу я отримати повернення коштів?", + "go.faq.a10": + "Ви можете претендувати на повернення, якщо кошти було списано протягом останніх 14 днів і ви не використали ліміт Go протягом цього розрахункового періоду. {{contact}}, щоб подати запит на повернення.", "zen.api.error.rateLimitExceeded": "Перевищено ліміт запитів. Спробуйте пізніше.", "zen.api.error.modelNotSupported": "Модель {{model}} не підтримується", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index 8be6f4b20eb9..4e10ae5d2003 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -59,6 +59,7 @@ export const dict = { "common.cancel": "取消", "common.creating": "正在创建...", "common.create": "创建", + "common.contactUs": "联系我们", "common.videoUnsupported": "您的浏览器不支持 video 标签。", "common.figure": "图 {{n}}.", @@ -218,6 +219,9 @@ export const dict = { "zen.faq.q8": "我可以在其他编程代理中使用 Zen 吗?", "zen.faq.a8": "虽然 Zen 与 OpenCode 配合效果极佳,但您可以在任何代理中使用 Zen。请按照您首选编程代理中的设置说明进行操作。", + "zen.faq.q9": "我可以退款吗?", + "zen.faq.a9": + "如果扣款发生在过去 14 天内,并且您尚未使用该次购买的任何额度,您可能符合退款条件。请{{contact}}申请退款。", "zen.cta.start": "开始使用 Zen", "zen.pricing.title": "充值 $20 (即用即付)", @@ -349,6 +353,9 @@ export const dict = { "go.faq.q9": "免费模型和 Go 之间的区别是什么?", "go.faq.a9": "免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 提供精选模型阵容,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60 的基础额度;具体额度可能因模型而异(实际请求计数因模型和使用情况而异)。", + "go.faq.q10": "我可以退款吗?", + "go.faq.a10": + "如果扣款发生在过去 14 天内,并且您在该计费周期内尚未使用任何 Go 额度,您可能符合退款条件。请{{contact}}申请退款。", "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 b1e7f3a3a5dd..2f3ba7115424 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -59,6 +59,7 @@ export const dict = { "common.cancel": "取消", "common.creating": "正在建立...", "common.create": "建立", + "common.contactUs": "聯絡我們", "common.videoUnsupported": "你的瀏覽器不支援 video 標籤。", "common.figure": "圖 {{n}}.", @@ -217,6 +218,9 @@ export const dict = { "zen.faq.q8": "我可以在其他編碼代理中使用 Zen 嗎?", "zen.faq.a8": "Zen 與 OpenCode 搭配得很好,但你也可以在任何代理中使用 Zen。請在你偏好的編碼代理中按照設定說明進行配置。", + "zen.faq.q9": "我可以退款嗎?", + "zen.faq.a9": + "若扣款發生在過去 14 天內,且你尚未使用該次購買的任何額度,你可能符合退款資格。請{{contact}}申請退款。", "zen.cta.start": "開始使用 Zen", "zen.pricing.title": "儲值 $20 即用即付餘額", @@ -349,6 +353,9 @@ export const dict = { "go.faq.q9": "免費模型與 Go 有什麼區別?", "go.faq.a9": "免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為 200 次請求/天。Go 提供精選模型陣容,並在滾動視窗(5 小時、每週和每月)內提供更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60 的基礎額度;具體額度可能因模型而異(實際請求數因模型和使用情況而異)。", + "go.faq.q10": "我可以退款嗎?", + "go.faq.a10": + "若扣款發生在過去 14 天內,且你在該計費期間尚未使用任何 Go 額度,你可能符合退款資格。請{{contact}}申請退款。", "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 3704e9634d72..a0789ce65f96 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -362,6 +362,15 @@ export default function Home() {
  • {i18n.t("go.faq.a8")}
  • +
  • + + + {(part) => + part === "{{contact}}" ? {i18n.t("common.contactUs")} : part + } + + +
  • diff --git a/packages/console/app/src/routes/zen/index.tsx b/packages/console/app/src/routes/zen/index.tsx index 6285a0bd8a56..9d378078b93b 100644 --- a/packages/console/app/src/routes/zen/index.tsx +++ b/packages/console/app/src/routes/zen/index.tsx @@ -1,6 +1,7 @@ import "./index.css" import { createAsync, query } from "@solidjs/router" import { Title, Meta } from "@solidjs/meta" +import { For } from "solid-js" //import { HttpHeader } from "@solidjs/start" import zenLogoLight from "../../asset/zen-ornate-light.svg" import zenLogoDark from "../../asset/zen-ornate-dark.svg" @@ -321,6 +322,15 @@ export default function Home() {
  • {i18n.t("zen.faq.a8")}
  • +
  • + + + {(part) => + part === "{{contact}}" ? {i18n.t("common.contactUs")} : part + } + + +
  • From 337fd144d2ba144743368f78d9579a99cce175bd Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 6 Sep 2026 04:32:46 +0000 Subject: [PATCH 037/129] chore: generate --- packages/console/app/src/i18n/zht.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index 2f3ba7115424..81a8cf7196f3 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -219,8 +219,7 @@ export const dict = { "zen.faq.a8": "Zen 與 OpenCode 搭配得很好,但你也可以在任何代理中使用 Zen。請在你偏好的編碼代理中按照設定說明進行配置。", "zen.faq.q9": "我可以退款嗎?", - "zen.faq.a9": - "若扣款發生在過去 14 天內,且你尚未使用該次購買的任何額度,你可能符合退款資格。請{{contact}}申請退款。", + "zen.faq.a9": "若扣款發生在過去 14 天內,且你尚未使用該次購買的任何額度,你可能符合退款資格。請{{contact}}申請退款。", "zen.cta.start": "開始使用 Zen", "zen.pricing.title": "儲值 $20 即用即付餘額", From 23ec4f55c8bf4009068c44635a448d602914f1f7 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 15:40:58 -0500 Subject: [PATCH 038/129] fix(provider): bump OpenAI SDK to 3.0.88 (#47659) Co-authored-by: rekram1-node Co-authored-by: GuestAUser <62957566+GuestAUser@users.noreply.github.com> --- bun.lock | 12 ++++++------ packages/core/package.json | 2 +- packages/opencode/package.json | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bun.lock b/bun.lock index ac048b971514..73c95f3be268 100644 --- a/bun.lock +++ b/bun.lock @@ -306,7 +306,7 @@ "@ai-sdk/google-vertex": "4.0.181", "@ai-sdk/groq": "3.0.31", "@ai-sdk/mistral": "3.0.51", - "@ai-sdk/openai": "3.0.84", + "@ai-sdk/openai": "3.0.88", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", @@ -581,7 +581,7 @@ "@ai-sdk/google-vertex": "4.0.181", "@ai-sdk/groq": "3.0.31", "@ai-sdk/mistral": "3.0.51", - "@ai-sdk/openai": "3.0.84", + "@ai-sdk/openai": "3.0.88", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", @@ -6019,7 +6019,7 @@ "@opencode-ai/app/@opencode-ai/client": ["@opencode-ai/client@vendor/opencode-ai-client-1.17.13-v2.tgz", {}, "sha512-332kgNifvpQOF9e3UA+pIa5xPrMhLaQkUiNiO+meS0Ba9HjSE6hfsWnEojMkD0DPSLqPP6rCF1dDoF7U0Y0OCQ=="], - "@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="], + "@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.88", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-6M4+bxK/UMijDYK2ia1hCKYma1iajFYxHDlhbinFUdhH2WbFsjoZRMpSjEMlTOBptqmh2J1JclKnprGulF8WwQ=="], "@opencode-ai/core/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], @@ -6395,7 +6395,7 @@ "opencode/@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.60", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.54", "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Rnok3cThg6awBwaDSyiZpgRpbV7pqxGYrA89LODCo5cuEHeP2h0AM0lLHP7zIkclAdXfOm4wldKi/S2T/DGCOw=="], - "opencode/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="], + "opencode/@ai-sdk/openai": ["@ai-sdk/openai@3.0.88", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-6M4+bxK/UMijDYK2ia1hCKYma1iajFYxHDlhbinFUdhH2WbFsjoZRMpSjEMlTOBptqmh2J1JclKnprGulF8WwQ=="], "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], @@ -6915,7 +6915,7 @@ "@opencode-ai/core/@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="], - "@opencode-ai/core/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="], + "@opencode-ai/core/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="], "@opencode-ai/desktop/@actions/artifact/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], @@ -7163,7 +7163,7 @@ "opencode/@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="], - "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="], + "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="], "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], diff --git a/packages/core/package.json b/packages/core/package.json index 5e6ccd4c4738..eb6bf7cf1065 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -73,7 +73,7 @@ "@ai-sdk/google-vertex": "4.0.181", "@ai-sdk/groq": "3.0.31", "@ai-sdk/mistral": "3.0.51", - "@ai-sdk/openai": "3.0.84", + "@ai-sdk/openai": "3.0.88", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 4d3710b62cb5..75f8af9ef34e 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -67,7 +67,7 @@ "@ai-sdk/google-vertex": "4.0.181", "@ai-sdk/groq": "3.0.31", "@ai-sdk/mistral": "3.0.51", - "@ai-sdk/openai": "3.0.84", + "@ai-sdk/openai": "3.0.88", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", From c470c79513f78aabb2ff88a8c8f7a3a22c4e97af Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 6 Sep 2026 20:59:14 +0000 Subject: [PATCH 039/129] 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 b36141f400cf..46f4c3a5fe68 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-2kzFIn42mD7ZDu/+6lWctqjZ/lIVZZfjZhmF/ymhF54=", - "aarch64-linux": "sha256-4HlReGD3gYfyyfnY/FQ46Ov+g3ZGV3sBYQ9p1bS9YAY=", - "aarch64-darwin": "sha256-dkfCoH/sW9XBPZ7XhnUoE54TY1lcfNvZ5wQQLr7gKiQ=", - "x86_64-darwin": "sha256-32t1JEcibZ4OrornIgfWOGQA87FOXjrxrxlDrflh7Ss=" + "x86_64-linux": "sha256-iIlDsO3XA7lneY0Bd+zgxUaGEjs5Oi8Mle6H4KGX6lE=", + "aarch64-linux": "sha256-IX92GciA8gvg0AwF7URBb7bqKZ0dB8XV3HECd5iOceY=", + "aarch64-darwin": "sha256-3asRb5Z40URPuUqn6enNsQyauwkRXuncja5OCnhHn+s=", + "x86_64-darwin": "sha256-fV7KLEoqWOPcM+MD/uspsYGmPBdy/vkuAK5rKWWrC2c=" } } From bec9ee41afc64c0333f7ace8e73ea6ab47f30213 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 16:34:09 -0500 Subject: [PATCH 040/129] fix(provider): bump Azure SDK to 3.0.93 (#47664) Co-authored-by: rekram1-node --- bun.lock | 30 +++++++++++++++++++++++------- packages/core/package.json | 2 +- packages/opencode/package.json | 2 +- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/bun.lock b/bun.lock index 73c95f3be268..2094d1517848 100644 --- a/bun.lock +++ b/bun.lock @@ -297,7 +297,7 @@ "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/amazon-bedrock": "4.0.166", "@ai-sdk/anthropic": "3.0.111", - "@ai-sdk/azure": "3.0.88", + "@ai-sdk/azure": "3.0.93", "@ai-sdk/cerebras": "2.0.41", "@ai-sdk/cohere": "3.0.27", "@ai-sdk/deepinfra": "2.0.41", @@ -572,7 +572,7 @@ "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/amazon-bedrock": "4.0.166", "@ai-sdk/anthropic": "3.0.111", - "@ai-sdk/azure": "3.0.88", + "@ai-sdk/azure": "3.0.93", "@ai-sdk/cerebras": "2.0.60", "@ai-sdk/cohere": "3.0.27", "@ai-sdk/deepinfra": "2.0.41", @@ -1175,7 +1175,7 @@ "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.111", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.46" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-atgBW8jZPr/KuaKX5FvDIHuXBI8VCol6kVeoD4P0657+VXR73QsLogXQVN/Zt5FHtq9WzpdIZseCJiXPqkgwwA=="], - "@ai-sdk/azure": ["@ai-sdk/azure@3.0.88", "", { "dependencies": { "@ai-sdk/deepseek": "2.0.47", "@ai-sdk/openai": "3.0.84", "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RRjZkB1lYplh8dpBarnvkl1j7sYLHsyXua7erL3oNcMK7fHcl4bPO5C7iQhD1O/DqD/zCceDifnege1s+8yEvw=="], + "@ai-sdk/azure": ["@ai-sdk/azure@3.0.93", "", { "dependencies": { "@ai-sdk/deepseek": "2.0.50", "@ai-sdk/openai": "3.0.88", "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9nKNmRtopjE3+L+286/4sLBDfDvmdsL7fiQXG0TE38NywxkDDLUhdr0KSllkRQ+m1/8bO7x4rIVhHSjQQiqmnQ=="], "@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.41", "", { "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-kDMEpjaRdRXIUi1EH8WHwLRahyDTYv9SAJnP6VCCeq8X+tVqZbMLCqqxSG5dRknrI65ucjvzQt+FiDKTAa7AHg=="], @@ -1185,7 +1185,7 @@ "@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@2.0.41", "", { "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-y6RoOP7DGWmDSiSxrUSt5p18sbz+Ixe5lMVPmdE7x+Tr5rlrzvftyHhjWHfqlAtoYERZTGFbP6tPW1OfQcrb4A=="], - "@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.47", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MzcQ321JO8OY+TVLFI81A7cIIuoeLLxrLCDD+8C1E3Ro6UFyfMtRXo9bw9OhTMRSDMo6hgSDOo4Fekz8aJtQYQ=="], + "@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-pnl22aGBIdpK0sXSJN4zDykIXTdyO9m3gFhno3SBPpdwT4Tq041Uy0i/EDWSxIiFa9JEokFGiCt4NNzcE5ntRA=="], "@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@2.0.51", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XFONX6rAsu6d13cJVUfZfkq4a+qdThlxvoEfzYlSRa1AvALlzwX7Y6bunXxevuifT3n882+nDdWrdiYvFP0+Fw=="], @@ -5645,11 +5645,11 @@ "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.46", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^6.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tEtld97plCFiYevsJuOkGkeuhQndeMWFBVrJS4AjnbD5AqrNSXRCe0p+BZ3Cju/sxDeeZ9ym3q9YUV8fASA7aQ=="], - "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="], + "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.88", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-6M4+bxK/UMijDYK2ia1hCKYma1iajFYxHDlhbinFUdhH2WbFsjoZRMpSjEMlTOBptqmh2J1JclKnprGulF8WwQ=="], "@ai-sdk/azure/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="], - "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="], + "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="], "@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], @@ -5663,7 +5663,7 @@ "@ai-sdk/deepseek/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="], - "@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="], + "@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="], "@ai-sdk/elevenlabs/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="], @@ -6161,10 +6161,14 @@ "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.153", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.110", "@ai-sdk/openai": "3.0.96", "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45", "@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-iEXrLgWylCHJmznqlKLU3CqRh8UWibv+illrwmsk136FVBBvyXiGnpQrI1pGWCScVLQjBQSFQu7GJDkUEomf/A=="], + "ai-gateway-provider/@ai-sdk/azure": ["@ai-sdk/azure@3.0.88", "", { "dependencies": { "@ai-sdk/deepseek": "2.0.47", "@ai-sdk/openai": "3.0.84", "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RRjZkB1lYplh8dpBarnvkl1j7sYLHsyXua7erL3oNcMK7fHcl4bPO5C7iQhD1O/DqD/zCceDifnege1s+8yEvw=="], + "ai-gateway-provider/@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.60", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.54", "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Rnok3cThg6awBwaDSyiZpgRpbV7pqxGYrA89LODCo5cuEHeP2h0AM0lLHP7zIkclAdXfOm4wldKi/S2T/DGCOw=="], "ai-gateway-provider/@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cXLjIsSzUriPHe704IH6d+ipJ/OvczTB700p9Zma7DPgQzvxG/diyr8q/2LEsbTRiTopiKhky8dn1PJNQcJToQ=="], + "ai-gateway-provider/@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.47", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MzcQ321JO8OY+TVLFI81A7cIIuoeLLxrLCDD+8C1E3Ro6UFyfMtRXo9bw9OhTMRSDMo6hgSDOo4Fekz8aJtQYQ=="], + "ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@3.0.108", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kwvYpRNghqt0VRKE7Hx1UWZQCUJJFqUITj24baxy+ApS0Hru0PkBJHD75a36Wc+e6e+wHcKR2MconTeJiBZigA=="], "ai-gateway-provider/@ai-sdk/groq": ["@ai-sdk/groq@3.0.59", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-X4h60TGq4pIOXPsthatUr+bfTaYCaKGX597hG9JgcueEl4+nboCdw99ixjFKGkvYlBJwLCCfI957EmGA2QlF0w=="], @@ -6985,6 +6989,10 @@ "ai-gateway-provider/@ai-sdk/amazon-bedrock/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + "ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="], + + "ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="], + "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OyXt0zK8y2/ZIyWlbxTv2r1M7AK227S+Gl4BYOEF42q0wz1n5m4fwR8L4Fy/MQ4Ho6xje47MPsFcRdIqIyP6Rw=="], "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="], @@ -6995,6 +7003,10 @@ "ai-gateway-provider/@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="], + "ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="], + + "ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="], + "ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="], "ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="], @@ -7431,12 +7443,16 @@ "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "ai-gateway-provider/@ai-sdk/cohere/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "ai-gateway-provider/@ai-sdk/cohere/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], + "ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], diff --git a/packages/core/package.json b/packages/core/package.json index eb6bf7cf1065..47b733dfd6cc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -64,7 +64,7 @@ "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/amazon-bedrock": "4.0.166", "@ai-sdk/anthropic": "3.0.111", - "@ai-sdk/azure": "3.0.88", + "@ai-sdk/azure": "3.0.93", "@ai-sdk/cerebras": "2.0.41", "@ai-sdk/cohere": "3.0.27", "@ai-sdk/deepinfra": "2.0.41", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 75f8af9ef34e..3fe792381f77 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -58,7 +58,7 @@ "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/amazon-bedrock": "4.0.166", "@ai-sdk/anthropic": "3.0.111", - "@ai-sdk/azure": "3.0.88", + "@ai-sdk/azure": "3.0.93", "@ai-sdk/cerebras": "2.0.60", "@ai-sdk/cohere": "3.0.27", "@ai-sdk/deepinfra": "2.0.41", From ea2d59d7ca8028951a16d4ebc558104258440bf9 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 16:48:43 -0500 Subject: [PATCH 041/129] fix(provider): preserve explicit OpenAI service tiers (#47671) Co-authored-by: rekram1-node --- bun.lock | 1 + package.json | 3 +- patches/@ai-sdk%2Fopenai@3.0.88.patch | 280 ++++++++++++++++++++++++++ 3 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 patches/@ai-sdk%2Fopenai@3.0.88.patch diff --git a/bun.lock b/bun.lock index 2094d1517848..adfb02af1dfb 100644 --- a/bun.lock +++ b/bun.lock @@ -1064,6 +1064,7 @@ "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch", "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch", + "@ai-sdk/openai@3.0.88": "patches/@ai-sdk%2Fopenai@3.0.88.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch", "@ai-sdk/anthropic@3.0.111": "patches/@ai-sdk%2Fanthropic@3.0.111.patch", diff --git a/package.json b/package.json index 8d58ace1b453..a3f9544410da 100644 --- a/package.json +++ b/package.json @@ -162,6 +162,7 @@ "@ai-sdk/openai-compatible@2.0.41": "patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch", "@ai-sdk/groq@3.0.31": "patches/@ai-sdk%2Fgroq@3.0.31.patch", "@ai-sdk/anthropic@3.0.111": "patches/@ai-sdk%2Fanthropic@3.0.111.patch", - "@ai-sdk/amazon-bedrock@4.0.166": "patches/@ai-sdk%2Famazon-bedrock@4.0.166.patch" + "@ai-sdk/amazon-bedrock@4.0.166": "patches/@ai-sdk%2Famazon-bedrock@4.0.166.patch", + "@ai-sdk/openai@3.0.88": "patches/@ai-sdk%2Fopenai@3.0.88.patch" } } diff --git a/patches/@ai-sdk%2Fopenai@3.0.88.patch b/patches/@ai-sdk%2Fopenai@3.0.88.patch new file mode 100644 index 000000000000..5d0f3f7f81fe --- /dev/null +++ b/patches/@ai-sdk%2Fopenai@3.0.88.patch @@ -0,0 +1,280 @@ +diff --git a/dist/index.js b/dist/index.js +index 5fc82d7ca0f5fba1da3b24c15bb714f51b5aa178..e1a3e2c97774af3a5688c7052bd9a07d692fcc8f 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -1035,22 +1035,6 @@ var OpenAIChatLanguageModel = class { + }); + } + } +- if (openaiOptions.serviceTier === "flex" && !modelCapabilities.supportsFlexProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "flex processing is only available for o3, o4-mini, and gpt-5 models" +- }); +- baseArgs.service_tier = void 0; +- } +- if (openaiOptions.serviceTier === "priority" && !modelCapabilities.supportsPriorityProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported" +- }); +- baseArgs.service_tier = void 0; +- } + const { + tools: openaiTools2, + toolChoice: openaiToolChoice, +@@ -5454,22 +5438,6 @@ var OpenAIResponsesLanguageModel = class { + }); + } + } +- if ((openaiOptions == null ? void 0 : openaiOptions.serviceTier) === "flex" && !modelCapabilities.supportsFlexProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "flex processing is only available for o3, o4-mini, and gpt-5 models" +- }); +- delete baseArgs.service_tier; +- } +- if ((openaiOptions == null ? void 0 : openaiOptions.serviceTier) === "priority" && !modelCapabilities.supportsPriorityProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported" +- }); +- delete baseArgs.service_tier; +- } + const shellToolEnvType = (_k = (_j = (_i = tools == null ? void 0 : tools.find( + (tool) => tool.type === "provider" && tool.id === "openai.shell" + )) == null ? void 0 : _i.args) == null ? void 0 : _j.environment) == null ? void 0 : _k.type; +diff --git a/dist/index.mjs b/dist/index.mjs +index 589c4f76955bee692b8af36ea1d9a15121695d4a..6838ea0a362c8fe774527bb1126b8031ebfd3fab 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -1031,22 +1031,6 @@ var OpenAIChatLanguageModel = class { + }); + } + } +- if (openaiOptions.serviceTier === "flex" && !modelCapabilities.supportsFlexProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "flex processing is only available for o3, o4-mini, and gpt-5 models" +- }); +- baseArgs.service_tier = void 0; +- } +- if (openaiOptions.serviceTier === "priority" && !modelCapabilities.supportsPriorityProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported" +- }); +- baseArgs.service_tier = void 0; +- } + const { + tools: openaiTools2, + toolChoice: openaiToolChoice, +@@ -5557,22 +5541,6 @@ var OpenAIResponsesLanguageModel = class { + }); + } + } +- if ((openaiOptions == null ? void 0 : openaiOptions.serviceTier) === "flex" && !modelCapabilities.supportsFlexProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "flex processing is only available for o3, o4-mini, and gpt-5 models" +- }); +- delete baseArgs.service_tier; +- } +- if ((openaiOptions == null ? void 0 : openaiOptions.serviceTier) === "priority" && !modelCapabilities.supportsPriorityProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported" +- }); +- delete baseArgs.service_tier; +- } + const shellToolEnvType = (_k = (_j = (_i = tools == null ? void 0 : tools.find( + (tool) => tool.type === "provider" && tool.id === "openai.shell" + )) == null ? void 0 : _i.args) == null ? void 0 : _j.environment) == null ? void 0 : _k.type; +diff --git a/dist/internal/index.js b/dist/internal/index.js +index ad78b5df9359509091f4b8a1751dc34c03c74e32..ed92f56d8fe9c1442f741ef8d42d592ce9b28643 100644 +--- a/dist/internal/index.js ++++ b/dist/internal/index.js +@@ -1070,22 +1070,6 @@ var OpenAIChatLanguageModel = class { + }); + } + } +- if (openaiOptions.serviceTier === "flex" && !modelCapabilities.supportsFlexProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "flex processing is only available for o3, o4-mini, and gpt-5 models" +- }); +- baseArgs.service_tier = void 0; +- } +- if (openaiOptions.serviceTier === "priority" && !modelCapabilities.supportsPriorityProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported" +- }); +- baseArgs.service_tier = void 0; +- } + const { + tools: openaiTools, + toolChoice: openaiToolChoice, +@@ -5724,22 +5708,6 @@ var OpenAIResponsesLanguageModel = class { + }); + } + } +- if ((openaiOptions == null ? void 0 : openaiOptions.serviceTier) === "flex" && !modelCapabilities.supportsFlexProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "flex processing is only available for o3, o4-mini, and gpt-5 models" +- }); +- delete baseArgs.service_tier; +- } +- if ((openaiOptions == null ? void 0 : openaiOptions.serviceTier) === "priority" && !modelCapabilities.supportsPriorityProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported" +- }); +- delete baseArgs.service_tier; +- } + const shellToolEnvType = (_k = (_j = (_i = tools == null ? void 0 : tools.find( + (tool) => tool.type === "provider" && tool.id === "openai.shell" + )) == null ? void 0 : _i.args) == null ? void 0 : _j.environment) == null ? void 0 : _k.type; +diff --git a/dist/internal/index.mjs b/dist/internal/index.mjs +index db7d6741c9ea615d577ea07722b469c144066268..f6b41b2832d2c8392072e0ecc1612e9e18b1b48e 100644 +--- a/dist/internal/index.mjs ++++ b/dist/internal/index.mjs +@@ -1023,22 +1023,6 @@ var OpenAIChatLanguageModel = class { + }); + } + } +- if (openaiOptions.serviceTier === "flex" && !modelCapabilities.supportsFlexProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "flex processing is only available for o3, o4-mini, and gpt-5 models" +- }); +- baseArgs.service_tier = void 0; +- } +- if (openaiOptions.serviceTier === "priority" && !modelCapabilities.supportsPriorityProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported" +- }); +- baseArgs.service_tier = void 0; +- } + const { + tools: openaiTools, + toolChoice: openaiToolChoice, +@@ -5802,22 +5786,6 @@ var OpenAIResponsesLanguageModel = class { + }); + } + } +- if ((openaiOptions == null ? void 0 : openaiOptions.serviceTier) === "flex" && !modelCapabilities.supportsFlexProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "flex processing is only available for o3, o4-mini, and gpt-5 models" +- }); +- delete baseArgs.service_tier; +- } +- if ((openaiOptions == null ? void 0 : openaiOptions.serviceTier) === "priority" && !modelCapabilities.supportsPriorityProcessing) { +- warnings.push({ +- type: "unsupported", +- feature: "serviceTier", +- details: "priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported" +- }); +- delete baseArgs.service_tier; +- } + const shellToolEnvType = (_k = (_j = (_i = tools == null ? void 0 : tools.find( + (tool) => tool.type === "provider" && tool.id === "openai.shell" + )) == null ? void 0 : _i.args) == null ? void 0 : _j.environment) == null ? void 0 : _k.type; +diff --git a/src/chat/openai-chat-language-model.ts b/src/chat/openai-chat-language-model.ts +index 009a58deea236c2be03adc5cd03f98ed9050fd7b..a606675d82fc72a0bab8d52ac99866100e7ffb1d 100644 +--- a/src/chat/openai-chat-language-model.ts ++++ b/src/chat/openai-chat-language-model.ts +@@ -267,34 +267,6 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 { + } + } + +- // Validate flex processing support +- if ( +- openaiOptions.serviceTier === 'flex' && +- !modelCapabilities.supportsFlexProcessing +- ) { +- warnings.push({ +- type: 'unsupported', +- feature: 'serviceTier', +- details: +- 'flex processing is only available for o3, o4-mini, and gpt-5 models', +- }); +- baseArgs.service_tier = undefined; +- } +- +- // Validate priority processing support +- if ( +- openaiOptions.serviceTier === 'priority' && +- !modelCapabilities.supportsPriorityProcessing +- ) { +- warnings.push({ +- type: 'unsupported', +- feature: 'serviceTier', +- details: +- 'priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported', +- }); +- baseArgs.service_tier = undefined; +- } +- + const { + tools: openaiTools, + toolChoice: openaiToolChoice, +diff --git a/src/responses/openai-responses-language-model.ts b/src/responses/openai-responses-language-model.ts +index 926e7ec713480353b969901f19c5d1d8cc883dab..ed457ce8f07a60ed48c47a3cbf11b2a7c6cf6415 100644 +--- a/src/responses/openai-responses-language-model.ts ++++ b/src/responses/openai-responses-language-model.ts +@@ -433,36 +433,6 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 { + } + } + +- // Validate flex processing support +- if ( +- openaiOptions?.serviceTier === 'flex' && +- !modelCapabilities.supportsFlexProcessing +- ) { +- warnings.push({ +- type: 'unsupported', +- feature: 'serviceTier', +- details: +- 'flex processing is only available for o3, o4-mini, and gpt-5 models', +- }); +- // Remove from args if not supported +- delete (baseArgs as any).service_tier; +- } +- +- // Validate priority processing support +- if ( +- openaiOptions?.serviceTier === 'priority' && +- !modelCapabilities.supportsPriorityProcessing +- ) { +- warnings.push({ +- type: 'unsupported', +- feature: 'serviceTier', +- details: +- 'priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported', +- }); +- // Remove from args if not supported +- delete (baseArgs as any).service_tier; +- } +- + const shellToolEnvType = ( + tools?.find( + tool => tool.type === 'provider' && tool.id === 'openai.shell', From e207624c48159b03dbe17dbc8e51bbcf23e72df5 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sun, 6 Sep 2026 22:05:22 +0000 Subject: [PATCH 042/129] 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 46f4c3a5fe68..61e0192ecaf3 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-iIlDsO3XA7lneY0Bd+zgxUaGEjs5Oi8Mle6H4KGX6lE=", - "aarch64-linux": "sha256-IX92GciA8gvg0AwF7URBb7bqKZ0dB8XV3HECd5iOceY=", - "aarch64-darwin": "sha256-3asRb5Z40URPuUqn6enNsQyauwkRXuncja5OCnhHn+s=", - "x86_64-darwin": "sha256-fV7KLEoqWOPcM+MD/uspsYGmPBdy/vkuAK5rKWWrC2c=" + "x86_64-linux": "sha256-I+R5gk2EILzPEDpt6lmMoWt8SFOp/r2nj6guBjPdWnQ=", + "aarch64-linux": "sha256-8gA5awtYgzjnTiY5++b3gxwVvva4oG+7GjRxViQOsp8=", + "aarch64-darwin": "sha256-if/UVGbRaMLEtfjkbFbGy6pSAFnsi6Ydac6/q25xdkk=", + "x86_64-darwin": "sha256-fI3PEftScGvBPwtUG4K8Y8DmenwMIlU9M1HH8lyuEnw=" } } From 13e3744f76e770b4cf18a2d1022d3948b0c9f5b7 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 6 Sep 2026 23:42:25 -0400 Subject: [PATCH 043/129] docs(go): document client session compatibility --- packages/web/src/content/docs/go.mdx | 82 +++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index ddd14d69711c..56ad3501960c 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -93,16 +93,84 @@ The list of models may change as we test and add new ones. ## Where can I use it? -OpenCode Go is designed to be used with [OpenCode](https://opencode.ai) and other -popular coding agents that produce a similar types of requests. +OpenCode Go is designed for [OpenCode](https://opencode.ai) and other coding agents +that produce similar types of requests. Traffic is monitored for abuse that +degrades the experience for other users. + +Your client should: + +1. Avoid generating abusive traffic. +2. Identify itself with its own user agent, such as `my-coding-agent/1.0`, rather + than a generic SDK or HTTP-library name. +3. Send a stable session ID for each conversation so we can optimize routing and + prompt caching. + +### Clients with session support + +Use an up-to-date client and its OpenCode Go integration where available. + +| Client | Session support | +| --- | --- | +| **OpenCode** | Sends the session ID automatically. Update older installations. | +| **Pi** | Current builds send session information for OpenCode. Update older installations. | +| **Claude Code** | Go recognizes its native session header. No custom-header wrapper is needed. | +| **ZCode** | Go recognizes its native session header. Our [request for `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) remains open, but it is no longer necessary to send that specific header. | +| **Codex** | Go recognizes its native session header. Some versions and proxy setups still omit it; preserve the session header when forwarding requests. | +| **jcode** | Update to **v0.81.6 or later**, which includes the [session-header fix](https://github.com/1jehuang/jcode/issues/1167). | +| **Hermes** | Builds containing [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) send the header on main and auxiliary OpenCode requests. The fix was merged after v0.21.0; that release alone does not include it. | +| **Kilo Code CLI** | Builds containing [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) restore OpenCode session headers. This fix covers the CLI, not the VS Code extension. See [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Clients with outstanding integration work + +These clients have missing or incomplete session support in the versions we +investigated. The linked reports track fixes and workarounds. + +| Client | Status and tracking | +| --- | --- | +| **DeepSeek Harness** | Session information arrives on some model paths, but is missing on others. We recognize its native header; the remaining work is to send it across all adapters. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Automatic session-header support is requested in [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Automatic session-header support is requested in [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) has a proposed fix in [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), which has not yet merged. | +| **Cursor** | We have not verified automatic session support. Contact [Cursor support](https://forum.cursor.com/) about sending a stable conversation ID. | +| **WorkBuddy** | We have not verified automatic session support. Contact the team through [WorkBuddy](https://workbuddy.ai/) about sending a stable conversation ID. | +| **TauriTavern** | The maintainer reports a dedicated OpenCode provider with a stable ID, targeted at the Canary release. See [issue #221](https://github.com/Darkatse/TauriTavern/issues/221) for availability. This integration fix does not change Go's intended use for coding agents. | + +### SDKs and gateways + +SDKs do not necessarily own a conversation's lifecycle. If you build your own +coding client, supply the session ID in your application and give the client a +distinct user agent. If you use a gateway, preserve the ID from the original client. + +| Integration | What to do | +| --- | --- | +| **Vercel AI SDK** | Pass `x-opencode-session` through the `headers` option on each `generateText` or `streamText` call. [Issue #20271](https://github.com/vercel/ai/issues/20271) was closed: applications must supply the ID. | +| **OpenAI Agents SDK for Python** | Set `ModelSettings.extra_headers` with the session header. [Issue #4841](https://github.com/openai/openai-agents-python/issues/4841) was closed with this supported configuration. | +| **LangChain.js** | Add the header through your model client's request-header configuration. [Issue #11547](https://github.com/langchain-ai/langchainjs/issues/11547) tracks the request for automatic support. | +| **LiteLLM** | Configure your gateway to forward or supply a per-conversation session header. [Issue #39503](https://github.com/BerriAI/litellm/issues/39503) remains open. | +| **AxonHub** | Session-header support was added in the fix for [issue #2361](https://github.com/looplj/axonhub/issues/2361). Use a build containing that fix and the OpenCode Go channel, and pass a stable session ID from your client; a randomly generated fallback per request does not preserve conversation affinity. | +| **OpenAI / Anthropic SDKs, fetch, and other HTTP clients** | Add the session header and your application's user agent explicitly. A generic `OpenAI/Python`, `node`, `Bun`, or browser user agent does not identify the coding agent. | + +For example, create an ID when a conversation starts and reuse it on every +related request: + +```ts +// Persist this with the conversation; do not generate a new ID for each request. +const sessionID = crypto.randomUUID() +const headers = { + "user-agent": "my-coding-agent/1.0", + "x-opencode-session": sessionID, +} +``` -Traffic is monitored for abusive traffic that degrades the experience for other users. +Keep the same ID across follow-up messages, tool calls, retries, and resumed +conversations. Start a new ID for a new conversation rather than sharing one +fixed value across your entire installation. -To ensure your account does not get flagged, make sure the tool you're using +Go also accepts `x-claude-code-session-id`, `x-session-id`, `session-id`, +`session_id`, and `x-deepseek-harness-session-id`. Clients that already send one +of these do not need to duplicate it as `x-opencode-session`. -1\. does not generate abusive traffic -2\. properly identifies itself (no broad user agents)
    -3\. includes the `x-opencode-session` header so we can optimize prompt caching +--- ## Usage limits From 56d4fb24a1dd40e4ca2ab6a6d3bc5d62b5eb63be Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 7 Sep 2026 03:44:38 +0000 Subject: [PATCH 044/129] chore: generate --- packages/web/src/content/docs/go.mdx | 52 ++++++++++++++-------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index 56ad3501960c..5678047964ae 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -109,15 +109,15 @@ Your client should: Use an up-to-date client and its OpenCode Go integration where available. -| Client | Session support | -| --- | --- | -| **OpenCode** | Sends the session ID automatically. Update older installations. | -| **Pi** | Current builds send session information for OpenCode. Update older installations. | -| **Claude Code** | Go recognizes its native session header. No custom-header wrapper is needed. | -| **ZCode** | Go recognizes its native session header. Our [request for `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) remains open, but it is no longer necessary to send that specific header. | -| **Codex** | Go recognizes its native session header. Some versions and proxy setups still omit it; preserve the session header when forwarding requests. | -| **jcode** | Update to **v0.81.6 or later**, which includes the [session-header fix](https://github.com/1jehuang/jcode/issues/1167). | -| **Hermes** | Builds containing [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) send the header on main and auxiliary OpenCode requests. The fix was merged after v0.21.0; that release alone does not include it. | +| Client | Session support | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **OpenCode** | Sends the session ID automatically. Update older installations. | +| **Pi** | Current builds send session information for OpenCode. Update older installations. | +| **Claude Code** | Go recognizes its native session header. No custom-header wrapper is needed. | +| **ZCode** | Go recognizes its native session header. Our [request for `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) remains open, but it is no longer necessary to send that specific header. | +| **Codex** | Go recognizes its native session header. Some versions and proxy setups still omit it; preserve the session header when forwarding requests. | +| **jcode** | Update to **v0.81.6 or later**, which includes the [session-header fix](https://github.com/1jehuang/jcode/issues/1167). | +| **Hermes** | Builds containing [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) send the header on main and auxiliary OpenCode requests. The fix was merged after v0.21.0; that release alone does not include it. | | **Kilo Code CLI** | Builds containing [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) restore OpenCode session headers. This fix covers the CLI, not the VS Code extension. See [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Clients with outstanding integration work @@ -125,15 +125,15 @@ Use an up-to-date client and its OpenCode Go integration where available. These clients have missing or incomplete session support in the versions we investigated. The linked reports track fixes and workarounds. -| Client | Status and tracking | -| --- | --- | -| **DeepSeek Harness** | Session information arrives on some model paths, but is missing on others. We recognize its native header; the remaining work is to send it across all adapters. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | Automatic session-header support is requested in [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | Automatic session-header support is requested in [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) has a proposed fix in [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), which has not yet merged. | -| **Cursor** | We have not verified automatic session support. Contact [Cursor support](https://forum.cursor.com/) about sending a stable conversation ID. | -| **WorkBuddy** | We have not verified automatic session support. Contact the team through [WorkBuddy](https://workbuddy.ai/) about sending a stable conversation ID. | -| **TauriTavern** | The maintainer reports a dedicated OpenCode provider with a stable ID, targeted at the Canary release. See [issue #221](https://github.com/Darkatse/TauriTavern/issues/221) for availability. This integration fix does not change Go's intended use for coding agents. | +| Client | Status and tracking | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | Session information arrives on some model paths, but is missing on others. We recognize its native header; the remaining work is to send it across all adapters. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Automatic session-header support is requested in [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Automatic session-header support is requested in [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) has a proposed fix in [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), which has not yet merged. | +| **Cursor** | We have not verified automatic session support. Contact [Cursor support](https://forum.cursor.com/) about sending a stable conversation ID. | +| **WorkBuddy** | We have not verified automatic session support. Contact the team through [WorkBuddy](https://workbuddy.ai/) about sending a stable conversation ID. | +| **TauriTavern** | The maintainer reports a dedicated OpenCode provider with a stable ID, targeted at the Canary release. See [issue #221](https://github.com/Darkatse/TauriTavern/issues/221) for availability. This integration fix does not change Go's intended use for coding agents. | ### SDKs and gateways @@ -141,14 +141,14 @@ SDKs do not necessarily own a conversation's lifecycle. If you build your own coding client, supply the session ID in your application and give the client a distinct user agent. If you use a gateway, preserve the ID from the original client. -| Integration | What to do | -| --- | --- | -| **Vercel AI SDK** | Pass `x-opencode-session` through the `headers` option on each `generateText` or `streamText` call. [Issue #20271](https://github.com/vercel/ai/issues/20271) was closed: applications must supply the ID. | -| **OpenAI Agents SDK for Python** | Set `ModelSettings.extra_headers` with the session header. [Issue #4841](https://github.com/openai/openai-agents-python/issues/4841) was closed with this supported configuration. | -| **LangChain.js** | Add the header through your model client's request-header configuration. [Issue #11547](https://github.com/langchain-ai/langchainjs/issues/11547) tracks the request for automatic support. | -| **LiteLLM** | Configure your gateway to forward or supply a per-conversation session header. [Issue #39503](https://github.com/BerriAI/litellm/issues/39503) remains open. | -| **AxonHub** | Session-header support was added in the fix for [issue #2361](https://github.com/looplj/axonhub/issues/2361). Use a build containing that fix and the OpenCode Go channel, and pass a stable session ID from your client; a randomly generated fallback per request does not preserve conversation affinity. | -| **OpenAI / Anthropic SDKs, fetch, and other HTTP clients** | Add the session header and your application's user agent explicitly. A generic `OpenAI/Python`, `node`, `Bun`, or browser user agent does not identify the coding agent. | +| Integration | What to do | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Vercel AI SDK** | Pass `x-opencode-session` through the `headers` option on each `generateText` or `streamText` call. [Issue #20271](https://github.com/vercel/ai/issues/20271) was closed: applications must supply the ID. | +| **OpenAI Agents SDK for Python** | Set `ModelSettings.extra_headers` with the session header. [Issue #4841](https://github.com/openai/openai-agents-python/issues/4841) was closed with this supported configuration. | +| **LangChain.js** | Add the header through your model client's request-header configuration. [Issue #11547](https://github.com/langchain-ai/langchainjs/issues/11547) tracks the request for automatic support. | +| **LiteLLM** | Configure your gateway to forward or supply a per-conversation session header. [Issue #39503](https://github.com/BerriAI/litellm/issues/39503) remains open. | +| **AxonHub** | Session-header support was added in the fix for [issue #2361](https://github.com/looplj/axonhub/issues/2361). Use a build containing that fix and the OpenCode Go channel, and pass a stable session ID from your client; a randomly generated fallback per request does not preserve conversation affinity. | +| **OpenAI / Anthropic SDKs, fetch, and other HTTP clients** | Add the session header and your application's user agent explicitly. A generic `OpenAI/Python`, `node`, `Bun`, or browser user agent does not identify the coding agent. | For example, create an ID when a conversation starts and reuse it on every related request: From a03bba5f2c9453d2396cdf5d465ac71c493b3b07 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Mon, 7 Sep 2026 00:02:27 -0400 Subject: [PATCH 045/129] docs --- packages/web/src/content/docs/go.mdx | 52 ++++------------------------ 1 file changed, 6 insertions(+), 46 deletions(-) diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index 5678047964ae..17af20a1d3e7 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -99,19 +99,19 @@ degrades the experience for other users. Your client should: -1. Avoid generating abusive traffic. +1. Send typical coding agent traffic 2. Identify itself with its own user agent, such as `my-coding-agent/1.0`, rather than a generic SDK or HTTP-library name. -3. Send a stable session ID for each conversation so we can optimize routing and +3. Send a stable session ID in `x-opencode-session` for each conversation so we can optimize routing and prompt caching. -### Clients with session support +### Validated Clients -Use an up-to-date client and its OpenCode Go integration where available. +Besides OpenCode, the following clients have been validated to work properly +with OpenCode Go. Although we do not guarantee that they will continue to work in the future. | Client | Session support | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **OpenCode** | Sends the session ID automatically. Update older installations. | | **Pi** | Current builds send session information for OpenCode. Update older installations. | | **Claude Code** | Go recognizes its native session header. No custom-header wrapper is needed. | | **ZCode** | Go recognizes its native session header. Our [request for `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) remains open, but it is no longer necessary to send that specific header. | @@ -120,7 +120,7 @@ Use an up-to-date client and its OpenCode Go integration where available. | **Hermes** | Builds containing [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) send the header on main and auxiliary OpenCode requests. The fix was merged after v0.21.0; that release alone does not include it. | | **Kilo Code CLI** | Builds containing [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) restore OpenCode session headers. This fix covers the CLI, not the VS Code extension. See [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | -### Clients with outstanding integration work +### Known Problematic Clients These clients have missing or incomplete session support in the versions we investigated. The linked reports track fixes and workarounds. @@ -131,46 +131,6 @@ investigated. The linked reports track fixes and workarounds. | **GitHub Copilot Chat** | Automatic session-header support is requested in [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | | **Kimi Code** | Automatic session-header support is requested in [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | | **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) has a proposed fix in [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), which has not yet merged. | -| **Cursor** | We have not verified automatic session support. Contact [Cursor support](https://forum.cursor.com/) about sending a stable conversation ID. | -| **WorkBuddy** | We have not verified automatic session support. Contact the team through [WorkBuddy](https://workbuddy.ai/) about sending a stable conversation ID. | -| **TauriTavern** | The maintainer reports a dedicated OpenCode provider with a stable ID, targeted at the Canary release. See [issue #221](https://github.com/Darkatse/TauriTavern/issues/221) for availability. This integration fix does not change Go's intended use for coding agents. | - -### SDKs and gateways - -SDKs do not necessarily own a conversation's lifecycle. If you build your own -coding client, supply the session ID in your application and give the client a -distinct user agent. If you use a gateway, preserve the ID from the original client. - -| Integration | What to do | -| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **Vercel AI SDK** | Pass `x-opencode-session` through the `headers` option on each `generateText` or `streamText` call. [Issue #20271](https://github.com/vercel/ai/issues/20271) was closed: applications must supply the ID. | -| **OpenAI Agents SDK for Python** | Set `ModelSettings.extra_headers` with the session header. [Issue #4841](https://github.com/openai/openai-agents-python/issues/4841) was closed with this supported configuration. | -| **LangChain.js** | Add the header through your model client's request-header configuration. [Issue #11547](https://github.com/langchain-ai/langchainjs/issues/11547) tracks the request for automatic support. | -| **LiteLLM** | Configure your gateway to forward or supply a per-conversation session header. [Issue #39503](https://github.com/BerriAI/litellm/issues/39503) remains open. | -| **AxonHub** | Session-header support was added in the fix for [issue #2361](https://github.com/looplj/axonhub/issues/2361). Use a build containing that fix and the OpenCode Go channel, and pass a stable session ID from your client; a randomly generated fallback per request does not preserve conversation affinity. | -| **OpenAI / Anthropic SDKs, fetch, and other HTTP clients** | Add the session header and your application's user agent explicitly. A generic `OpenAI/Python`, `node`, `Bun`, or browser user agent does not identify the coding agent. | - -For example, create an ID when a conversation starts and reuse it on every -related request: - -```ts -// Persist this with the conversation; do not generate a new ID for each request. -const sessionID = crypto.randomUUID() -const headers = { - "user-agent": "my-coding-agent/1.0", - "x-opencode-session": sessionID, -} -``` - -Keep the same ID across follow-up messages, tool calls, retries, and resumed -conversations. Start a new ID for a new conversation rather than sharing one -fixed value across your entire installation. - -Go also accepts `x-claude-code-session-id`, `x-session-id`, `session-id`, -`session_id`, and `x-deepseek-harness-session-id`. Clients that already send one -of these do not need to duplicate it as `x-opencode-session`. - ---- ## Usage limits From 53fec37d8d2b9e0d92a1b4184e8df8f8480a2d26 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 7 Sep 2026 04:03:53 +0000 Subject: [PATCH 046/129] chore: generate --- packages/web/src/content/docs/go.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index 17af20a1d3e7..ab9e1bf18936 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -125,12 +125,12 @@ with OpenCode Go. Although we do not guarantee that they will continue to work i These clients have missing or incomplete session support in the versions we investigated. The linked reports track fixes and workarounds. -| Client | Status and tracking | -| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **DeepSeek Harness** | Session information arrives on some model paths, but is missing on others. We recognize its native header; the remaining work is to send it across all adapters. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | Automatic session-header support is requested in [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | Automatic session-header support is requested in [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) has a proposed fix in [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), which has not yet merged. | +| Client | Status and tracking | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **DeepSeek Harness** | Session information arrives on some model paths, but is missing on others. We recognize its native header; the remaining work is to send it across all adapters. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Automatic session-header support is requested in [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Automatic session-header support is requested in [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) has a proposed fix in [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), which has not yet merged. | ## Usage limits From f914cac3d47ef812fbea5b6cc0cbdc0357e03637 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Mon, 7 Sep 2026 01:49:16 -0400 Subject: [PATCH 047/129] docs --- packages/web/src/content/docs/go.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index ab9e1bf18936..ba4c06be2c48 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -112,12 +112,12 @@ with OpenCode Go. Although we do not guarantee that they will continue to work i | Client | Session support | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Pi** | Current builds send session information for OpenCode. Update older installations. | +| **Hermes** | Builds containing [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) send the header on main and auxiliary OpenCode requests. The fix was merged after v0.21.0; that release alone does not include it. | | **Claude Code** | Go recognizes its native session header. No custom-header wrapper is needed. | -| **ZCode** | Go recognizes its native session header. Our [request for `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) remains open, but it is no longer necessary to send that specific header. | | **Codex** | Go recognizes its native session header. Some versions and proxy setups still omit it; preserve the session header when forwarding requests. | +| **ZCode** | Go recognizes its native session header. Our [request for `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) remains open, but it is no longer necessary to send that specific header. | +| **Pi** | Current builds send session information for OpenCode. Update older installations. | | **jcode** | Update to **v0.81.6 or later**, which includes the [session-header fix](https://github.com/1jehuang/jcode/issues/1167). | -| **Hermes** | Builds containing [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) send the header on main and auxiliary OpenCode requests. The fix was merged after v0.21.0; that release alone does not include it. | | **Kilo Code CLI** | Builds containing [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) restore OpenCode session headers. This fix covers the CLI, not the VS Code extension. See [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Known Problematic Clients From 57ef3828431790c53f8f333c7ffbfe88770a1812 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 7 Sep 2026 01:11:00 -0500 Subject: [PATCH 048/129] feat(console): publish oauth client metadata document (#47737) --- .../src/routes/oauth/opencode/client.json.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 packages/console/app/src/routes/oauth/opencode/client.json.ts diff --git a/packages/console/app/src/routes/oauth/opencode/client.json.ts b/packages/console/app/src/routes/oauth/opencode/client.json.ts new file mode 100644 index 000000000000..edbdfc981698 --- /dev/null +++ b/packages/console/app/src/routes/oauth/opencode/client.json.ts @@ -0,0 +1,38 @@ +import type { APIEvent } from "@solidjs/start/server" + +// OAuth Client ID Metadata Document for the opencode client. +// Spec: https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/ +// +// When an MCP server's authorization server supports this, opencode sends this URL as its OAuth client_id +// instead of registering a new client. The authorization server fetches the document to learn our name and +// allowed redirect URIs. The client_id field must equal the exact URL the document was fetched from, so it is +// built from the request origin and stays valid on dev.opencode.ai as well as production. +// +// redirect_uris have no port because opencode binds an ephemeral port per login. RFC 8252 section 7.3 has +// authorization servers ignore the port when matching loopback redirects for native apps. +const PATH = "/oauth/opencode/client.json" + +const cache = "public, max-age=300" + +export function GET(event: APIEvent) { + const origin = new URL(event.request.url).origin + const document = { + client_id: origin + PATH, + client_name: "opencode", + client_uri: origin, + logo_uri: origin + "/web-app-manifest-512x512.png", + application_type: "native", + redirect_uris: ["http://127.0.0.1/callback", "http://localhost/callback"], + grant_types: ["authorization_code", "refresh_token"], + response_types: ["code"], + token_endpoint_auth_method: "none", + token_endpoint_auth_methods_supported: ["none"], + } + return new Response(JSON.stringify(document, null, 2), { + headers: { + "Content-Type": "application/json", + "Cache-Control": cache, + "Access-Control-Allow-Origin": "*", + }, + }) +} From 0b082b065d2b6bdf2cc6e6234176cbdaa2544d31 Mon Sep 17 00:00:00 2001 From: Vladimir Glafirov Date: Mon, 7 Sep 2026 18:03:30 +0200 Subject: [PATCH 049/129] chore: bump gitlab-ai-provider to 6.15.0 (#47792) --- bun.lock | 6 +++--- packages/core/package.json | 2 +- packages/opencode/package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bun.lock b/bun.lock index adfb02af1dfb..0efe1b76b05f 100644 --- a/bun.lock +++ b/bun.lock @@ -341,7 +341,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.14.0", + "gitlab-ai-provider": "6.15.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", @@ -634,7 +634,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.14.0", + "gitlab-ai-provider": "6.15.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", @@ -3846,7 +3846,7 @@ "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], - "gitlab-ai-provider": ["gitlab-ai-provider@6.14.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-qTkmFhcHXH0tjQN6i5D2lz9OpmYN0lwJdmFIXlsmy6IiYVV2dmsqMemlDNjpE5M8gdSC+61n4mByPzVsyBu0lg=="], + "gitlab-ai-provider": ["gitlab-ai-provider@6.15.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-hHJsb4WP+WPTMtMC0gZcTi0wreMvR6WdEPeUPRHNlyW7yX0HuFG7v1oZ/ethbF6y/FG3/7NcirNgjZLjNneW7g=="], "glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="], diff --git a/packages/core/package.json b/packages/core/package.json index 47b733dfd6cc..daa4c4e91e3d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -108,7 +108,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.14.0", + "gitlab-ai-provider": "6.15.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 3fe792381f77..d1aca264d548 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -120,7 +120,7 @@ "drizzle-orm": "catalog:", "effect": "catalog:", "fuzzysort": "3.1.0", - "gitlab-ai-provider": "6.14.0", + "gitlab-ai-provider": "6.15.0", "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", From ecbc6ccac85b3e8087b6445e584318419b9e2b34 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 7 Sep 2026 16:21:14 +0000 Subject: [PATCH 050/129] 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 61e0192ecaf3..845909e093d8 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-I+R5gk2EILzPEDpt6lmMoWt8SFOp/r2nj6guBjPdWnQ=", - "aarch64-linux": "sha256-8gA5awtYgzjnTiY5++b3gxwVvva4oG+7GjRxViQOsp8=", - "aarch64-darwin": "sha256-if/UVGbRaMLEtfjkbFbGy6pSAFnsi6Ydac6/q25xdkk=", - "x86_64-darwin": "sha256-fI3PEftScGvBPwtUG4K8Y8DmenwMIlU9M1HH8lyuEnw=" + "x86_64-linux": "sha256-tHl+UGkUbalkh+C5RDkRpZ3Q87tgvqnoF4xdih6QeOw=", + "aarch64-linux": "sha256-rdnbvlOOr/88SQ9zd+PfilJ4pQZdKplFyQGfdHBuBKQ=", + "aarch64-darwin": "sha256-28GpwYLMo2cN4Y9cY6/xn9R5EggFj/m1guuSxsbqisM=", + "x86_64-darwin": "sha256-eO0fs/0gwWwqPA8WElg8s7EZsxKd9J4Hws15P4xqHlc=" } } From eebd85f5d1eefcffa18523fa8aa614b3cb5613b4 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 8 Sep 2026 14:50:35 +0800 Subject: [PATCH 051/129] docs(go): translate client compatibility guidance (#47901) --- packages/web/src/content/docs/ar/go.mdx | 47 +++++++++++++++++----- packages/web/src/content/docs/bs/go.mdx | 47 +++++++++++++++++----- packages/web/src/content/docs/da/go.mdx | 47 +++++++++++++++++----- packages/web/src/content/docs/de/go.mdx | 47 +++++++++++++++++----- packages/web/src/content/docs/es/go.mdx | 47 +++++++++++++++++----- packages/web/src/content/docs/fr/go.mdx | 47 +++++++++++++++++----- packages/web/src/content/docs/it/go.mdx | 47 +++++++++++++++++----- packages/web/src/content/docs/ja/go.mdx | 38 ++++++++++++++--- packages/web/src/content/docs/ko/go.mdx | 38 ++++++++++++++--- packages/web/src/content/docs/nb/go.mdx | 47 +++++++++++++++++----- packages/web/src/content/docs/pl/go.mdx | 47 +++++++++++++++++----- packages/web/src/content/docs/pt-br/go.mdx | 47 +++++++++++++++++----- packages/web/src/content/docs/ru/go.mdx | 47 +++++++++++++++++----- packages/web/src/content/docs/th/go.mdx | 38 ++++++++++++++--- packages/web/src/content/docs/tr/go.mdx | 38 ++++++++++++++--- packages/web/src/content/docs/zh-cn/go.mdx | 35 +++++++++++++--- packages/web/src/content/docs/zh-tw/go.mdx | 35 +++++++++++++--- 17 files changed, 606 insertions(+), 133 deletions(-) diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 0cc8916a1574..7539981d899c 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -83,15 +83,44 @@ OpenCode Go هو اشتراك منخفض التكلفة بقيمة **$10/شهر ## أين يمكنني استخدامه؟ -صُمّم OpenCode Go للاستخدام مع [OpenCode](https://opencode.ai) وغيره من وكلاء البرمجة الشائعين الذين ينشئون أنواعًا مماثلة من الطلبات. - -تتم مراقبة حركة المرور لرصد الاستخدام المسيء الذي يؤدي إلى تدهور تجربة المستخدمين الآخرين. - -لتجنب وضع علامة على حسابك، تأكد من أن الأداة التي تستخدمها - -1\. لا تنشئ حركة مرور مسيئة -2\. تعرّف عن نفسها بشكل صحيح (من دون معرّفات وكيل مستخدم عامة) -3\. تتضمن ترويسة `x-opencode-session` حتى نتمكن من تحسين التخزين المؤقت للمطالبات +صُمّم OpenCode Go للاستخدام مع [OpenCode](https://opencode.ai) ووكلاء البرمجة الآخرين +الذين ينشئون أنواعًا مماثلة من الطلبات. وتتم مراقبة حركة المرور لرصد إساءة الاستخدام التي +تؤدي إلى تدهور تجربة المستخدمين الآخرين. + +ينبغي لعميلك أن: + +1. يرسل حركة المرور المعتادة لوكلاء البرمجة. +2. يعرّف عن نفسه باستخدام وكيل المستخدم الخاص به، مثل `my-coding-agent/1.0`، بدلًا + من اسم عام لحزمة SDK أو مكتبة HTTP. +3. يرسل معرّف جلسة ثابتًا في `x-opencode-session` لكل محادثة حتى نتمكن من تحسين التوجيه + والتخزين المؤقت للمطالبات. + +### العملاء الذين تم التحقق منهم + +بالإضافة إلى OpenCode، تم التحقق من أن العملاء التاليين يعملون بصورة صحيحة +مع OpenCode Go، مع أننا لا نضمن استمرارهم في العمل مستقبلًا. + +| العميل | دعم الجلسات | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Hermes** | ترسل البُنى التي تتضمن [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) الترويسة في طلبات OpenCode الرئيسية والمساعدة. دُمج الإصلاح بعد v0.21.0؛ ولا يتضمن ذلك الإصدار وحده الإصلاح. | +| **Claude Code** | يتعرف Go على ترويسة الجلسة الأصلية الخاصة به. ولا حاجة إلى غلاف لإضافة ترويسة مخصصة. | +| **Codex** | يتعرف Go على ترويسة الجلسة الأصلية الخاصة به. لا تزال بعض الإصدارات وإعدادات الوكيل تحذفها؛ لذا حافظ على ترويسة الجلسة عند إعادة توجيه الطلبات. | +| **ZCode** | يتعرف Go على ترويسة الجلسة الأصلية الخاصة به. لا يزال [طلبنا لإضافة `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) مفتوحًا، لكن إرسال هذه الترويسة بعينها لم يعد ضروريًا. | +| **Pi** | ترسل الإصدارات الحالية معلومات الجلسة إلى OpenCode. حدّث عمليات التثبيت الأقدم. | +| **jcode** | حدّث إلى **v0.81.6 أو إصدار أحدث**، إذ يتضمن [إصلاح ترويسة الجلسة](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | تعيد الإصدارات التي تتضمن [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) ترويسات جلسات OpenCode. يشمل هذا الإصلاح CLI، وليس إضافة VS Code. راجع [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### العملاء المعروف وجود مشكلات لديهم + +دعم الجلسات مفقود أو غير مكتمل في إصدارات هؤلاء العملاء التي +تحققنا منها. وتتابع البلاغات المرتبطة الإصلاحات والحلول البديلة. + +| العميل | الحالة والمتابعة | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **DeepSeek Harness** | تصل معلومات الجلسة عبر بعض مسارات النماذج، لكنها تكون مفقودة عبر مسارات أخرى. نتعرف على ترويسة الجلسة الأصلية الخاصة به؛ والعمل المتبقي هو إرسالها عبر جميع المحولات. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | طُلب دعم ترويسة الجلسة تلقائيًا في [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | طُلب دعم ترويسة الجلسة تلقائيًا في [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | يتضمن [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) إصلاحًا مقترحًا في [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327)، ولم يُدمج بعد. | ## حدود الاستخدام diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index 5b709650eaae..c379aa291b21 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -93,15 +93,44 @@ Lista modela se može mijenjati dok testiramo i dodajemo nove. ## Gdje ga mogu koristiti? -OpenCode Go je osmišljen za korištenje s [OpenCode-om](https://opencode.ai) i drugim popularnim agentima za programiranje koji generišu slične vrste zahtjeva. - -Saobraćaj se nadzire radi otkrivanja zloupotrebe koja narušava iskustvo drugih korisnika. - -Kako vaš račun ne bi bio označen, pobrinite se da alat koji koristite - -1\. ne generiše saobraćaj koji predstavlja zloupotrebu -2\. se ispravno identifikuje (bez generičkih User-Agent identifikatora) -3\. uključuje zaglavlje `x-opencode-session` kako bismo mogli optimizovati keširanje promptova +OpenCode Go je osmišljen za [OpenCode](https://opencode.ai) i druge agente za programiranje +koji šalju slične vrste zahtjeva. Saobraćaj se nadzire radi otkrivanja zloupotrebe koja +drugim korisnicima narušava iskustvo. + +Vaš klijent treba: + +1. Slati tipičan saobraćaj agenta za programiranje. +2. Identifikovati se vlastitim user agentom, kao što je `my-coding-agent/1.0`, a ne + generičkim nazivom SDK-a ili HTTP biblioteke. +3. Slati stabilan ID sesije u zaglavlju `x-opencode-session` za svaki razgovor kako bismo mogli optimizovati usmjeravanje i + keširanje promptova. + +### Provjereni klijenti + +Pored OpenCode-a, potvrđeno je da sljedeći klijenti ispravno rade +s OpenCode Go. Ipak, ne garantiramo da će nastaviti raditi i u budućnosti. + +| Klijent | Podrška za sesije | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Buildovi koji sadrže [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) šalju zaglavlje u glavnim i pomoćnim OpenCode zahtjevima. Ispravka je spojena nakon verzije v0.21.0; samo to izdanje je ne sadrži. | +| **Claude Code** | Go prepoznaje njegovo izvorno zaglavlje sesije. Wrapper za prilagođeno zaglavlje nije potreban. | +| **Codex** | Go prepoznaje njegovo izvorno zaglavlje sesije. Neke verzije i proxy konfiguracije ga i dalje izostavljaju; sačuvajte zaglavlje sesije pri prosljeđivanju zahtjeva. | +| **ZCode** | Go prepoznaje njegovo izvorno zaglavlje sesije. Naš [zahtjev za `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) i dalje je otvoren, ali slanje tog konkretnog zaglavlja više nije potrebno. | +| **Pi** | Trenutni buildovi šalju informacije o sesiji za OpenCode. Ažurirajte starije instalacije. | +| **jcode** | Ažurirajte na **v0.81.6 ili noviju**, koja uključuje [ispravku zaglavlja sesije](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Buildovi koji sadrže [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) vraćaju OpenCode zaglavlja sesije. Ova ispravka obuhvata CLI, ali ne i VS Code ekstenziju. Pogledajte [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Poznati problematični klijenti + +Ovim klijentima nedostaje podrška za sesije ili je ona nepotpuna u verzijama koje smo +istražili. Povezani izvještaji prate ispravke i zaobilazna rješenja. + +| Klijent | Status i praćenje | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | Informacije o sesiji stižu za neke putanje modela, ali nedostaju za druge. Prepoznajemo njegovo izvorno zaglavlje; preostaje da se ono šalje kroz sve adaptere. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Automatska podrška za zaglavlje sesije zatražena je u [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Automatska podrška za zaglavlje sesije zatražena je u [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) ima predloženu ispravku u [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), koji još nije spojen. | ## Ograničenja upotrebe diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 39504d3ff275..0208563cb649 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -93,15 +93,44 @@ Listen over modeller kan ændre sig, efterhånden som vi tester og tilføjer nye ## Hvor kan jeg bruge det? -OpenCode Go er designet til brug med [OpenCode](https://opencode.ai) og andre populære kodningsagenter, der genererer lignende typer anmodninger. - -Trafikken overvåges for misbrug, der forringer oplevelsen for andre brugere. - -For at sikre, at din konto ikke bliver markeret, skal du sørge for, at det værktøj, du bruger, - -1\. ikke genererer misbrugstrafik -2\. identificerer sig korrekt (ingen generiske User-Agent-identifikatorer) -3\. inkluderer `x-opencode-session`-headeren, så vi kan optimere prompt-caching +OpenCode Go er udviklet til [OpenCode](https://opencode.ai) og andre kodningsagenter, +der sender lignende typer anmodninger. Trafikken overvåges for misbrug, der +forringer oplevelsen for andre brugere. + +Din klient skal: + +1. Sende typisk trafik fra en kodningsagent. +2. Identificere sig med sin egen user agent, f.eks. `my-coding-agent/1.0`, i stedet + for et generisk navn på et SDK eller HTTP-bibliotek. +3. Sende et stabilt sessions-id i `x-opencode-session` for hver samtale, så vi kan optimere routing og + prompt-caching. + +### Validerede klienter + +Ud over OpenCode er følgende klienter blevet valideret til at fungere korrekt +med OpenCode Go. Vi garanterer dog ikke, at de fortsat vil fungere fremover. + +| Klient | Sessionsunderstøttelse | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Builds, der indeholder [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864), sender headeren i primære og sekundære OpenCode-anmodninger. Rettelsen blev merged efter v0.21.0; den version indeholder den ikke i sig selv. | +| **Claude Code** | Go genkender dens indbyggede sessionsheader. En wrapper til en brugerdefineret header er ikke nødvendig. | +| **Codex** | Go genkender dens indbyggede sessionsheader. Nogle versioner og proxyopsætninger udelader den stadig; bevar sessionsheaderen, når anmodninger videresendes. | +| **ZCode** | Go genkender dens indbyggede sessionsheader. Vores [anmodning om `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) er stadig åben, men det er ikke længere nødvendigt at sende netop denne header. | +| **Pi** | Aktuelle builds sender sessionsoplysninger til OpenCode. Opdater ældre installationer. | +| **jcode** | Opdater til **v0.81.6 eller nyere**, som indeholder [rettelsen til sessionsheaderen](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Builds, der indeholder [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752), gendanner OpenCode-sessionsheadere. Denne rettelse dækker CLI'en, men ikke VS Code-udvidelsen. Se [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Kendte problematiske klienter + +Disse klienter mangler sessionsunderstøttelse eller har ufuldstændig understøttelse i de versioner, vi +undersøgte. De linkede rapporter følger rettelser og løsninger. + +| Klient | Status og opfølgning | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | Sessionsoplysninger modtages på nogle modelstier, men mangler på andre. Vi genkender dens indbyggede header; det resterende arbejde er at sende den gennem alle adaptere. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Automatisk understøttelse af sessionsheaderen er efterspurgt i [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Automatisk understøttelse af sessionsheaderen er efterspurgt i [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) har en foreslået rettelse i [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), som endnu ikke er merged. | ## Forbrugsgrænser diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index 0e49e30a2d09..624bf78338b7 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -85,15 +85,44 @@ Die Liste der Modelle kann sich ändern, während wir neue testen und hinzufüge ## Wo kann ich es verwenden? -OpenCode Go wurde für die Verwendung mit [OpenCode](https://opencode.ai) und anderen beliebten Coding-Agenten entwickelt, die ähnliche Arten von Anfragen erzeugen. - -Der Datenverkehr wird auf missbräuchlichen Traffic überwacht, der das Nutzungserlebnis anderer beeinträchtigt. - -Damit dein Konto nicht markiert wird, stelle sicher, dass das von dir verwendete Tool - -1\. keinen missbräuchlichen Traffic erzeugt -2\. sich ordnungsgemäß identifiziert (keine allgemeinen User-Agents) -3\. den Header `x-opencode-session` enthält, damit wir das Prompt-Caching optimieren können +OpenCode Go wurde für [OpenCode](https://opencode.ai) und andere Coding-Agenten entwickelt, +die ähnliche Arten von Anfragen erzeugen. Der Datenverkehr wird auf Missbrauch überwacht, +der das Nutzungserlebnis anderer beeinträchtigt. + +Dein Client sollte: + +1. typischen Datenverkehr eines Coding-Agenten senden +2. sich mit einem eigenen User-Agent wie `my-coding-agent/1.0` identifizieren und + nicht mit dem allgemeinen Namen eines SDKs oder einer HTTP-Bibliothek. +3. für jede Unterhaltung eine stabile Sitzungs-ID in `x-opencode-session` senden, damit wir das Routing und + Prompt-Caching optimieren können. + +### Validierte Clients + +Neben OpenCode wurden die folgenden Clients für die ordnungsgemäße Verwendung mit +OpenCode Go validiert. Wir garantieren jedoch nicht, dass sie auch in Zukunft funktionieren werden. + +| Client | Sitzungsunterstützung | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Builds mit [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) senden den Header bei Haupt- und zusätzlichen OpenCode-Anfragen. Der Fix wurde nach v0.21.0 gemergt; diese Version allein enthält ihn nicht. | +| **Claude Code** | Go erkennt seinen nativen Sitzungs-Header. Es ist kein Wrapper für benutzerdefinierte Header erforderlich. | +| **Codex** | Go erkennt seinen nativen Sitzungs-Header. Einige Versionen und Proxy-Konfigurationen lassen ihn weiterhin weg; stelle sicher, dass der Sitzungs-Header beim Weiterleiten von Anfragen erhalten bleibt. | +| **ZCode** | Go erkennt seinen nativen Sitzungs-Header. Unsere [Anfrage für `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) ist weiterhin offen, aber es ist nicht mehr erforderlich, genau diesen Header zu senden. | +| **Pi** | Aktuelle Builds senden Sitzungsinformationen für OpenCode. Aktualisiere ältere Installationen. | +| **jcode** | Aktualisiere auf **v0.81.6 oder neuer**. Diese Version enthält den [Fix für den Sitzungs-Header](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Builds mit [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) stellen OpenCode-Sitzungs-Header wieder her. Dieser Fix gilt für die CLI, nicht für die VS-Code-Erweiterung. Siehe [Issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Bekannte problematische Clients + +Bei diesen Clients fehlt in den von uns untersuchten Versionen die Sitzungsunterstützung +oder sie ist unvollständig. Die verlinkten Berichte dokumentieren Fixes und Problemumgehungen. + +| Client | Status und Nachverfolgung | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **DeepSeek Harness** | Sitzungsinformationen werden bei einigen Modellpfaden übermittelt, fehlen aber bei anderen. Wir erkennen seinen nativen Header; dieser muss noch von allen Adaptern gesendet werden. [Diskussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Automatische Unterstützung für Sitzungs-Header wurde in [VS Code Issue #334186](https://github.com/microsoft/vscode/issues/334186) angefragt. | +| **Kimi Code** | Automatische Unterstützung für Sitzungs-Header wurde in [Issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) angefragt. | +| **MiMo Code** | Für [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) gibt es einen vorgeschlagenen Fix in [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), der noch nicht gemergt wurde. | ## Nutzungslimits diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index dd75cdda7751..3ec6cab9cc68 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -93,15 +93,44 @@ La lista de modelos puede cambiar a medida que probamos y agregamos otros nuevos ## ¿Dónde puedo usarlo? -OpenCode Go está diseñado para usarse con [OpenCode](https://opencode.ai) y otros agentes de programación populares que generan tipos de peticiones similares. - -El tráfico se supervisa para detectar tráfico abusivo que perjudique la experiencia de otros usuarios. - -Para evitar que tu cuenta sea marcada, asegúrate de que la herramienta que usas - -1\. no genere tráfico abusivo -2\. se identifique correctamente (sin agentes de usuario genéricos) -3\. incluya el encabezado `x-opencode-session` para que podamos optimizar el almacenamiento en caché de prompts +OpenCode Go está diseñado para [OpenCode](https://opencode.ai) y otros agentes de programación +que generan tipos de peticiones similares. El tráfico se supervisa para detectar abusos que +perjudiquen la experiencia de otros usuarios. + +Tu cliente debe: + +1. Enviar el tráfico habitual de un agente de programación +2. Identificarse con su propio agente de usuario, como `my-coding-agent/1.0`, en lugar + del nombre genérico de un SDK o una biblioteca HTTP. +3. Enviar un ID de sesión estable en `x-opencode-session` para cada conversación, de modo que podamos optimizar el enrutamiento y + el almacenamiento en caché de prompts. + +### Clientes validados + +Además de OpenCode, se ha validado que los siguientes clientes funcionan correctamente +con OpenCode Go. Sin embargo, no garantizamos que sigan funcionando en el futuro. + +| Cliente | Compatibilidad con sesiones | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Las compilaciones que incluyen el [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) envían el encabezado en las peticiones principales y auxiliares de OpenCode. La corrección se fusionó después de v0.21.0; esa versión por sí sola no la incluye. | +| **Claude Code** | Go reconoce su encabezado de sesión nativo. No se necesita ningún wrapper para encabezados personalizados. | +| **Codex** | Go reconoce su encabezado de sesión nativo. Algunas versiones y configuraciones de proxy aún lo omiten; conserva el encabezado de sesión al reenviar peticiones. | +| **ZCode** | Go reconoce su encabezado de sesión nativo. Nuestra [solicitud de `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) sigue abierta, pero ya no es necesario enviar ese encabezado específico. | +| **Pi** | Las compilaciones actuales envían información de sesión para OpenCode. Actualiza las instalaciones antiguas. | +| **jcode** | Actualiza a la versión **v0.81.6 o posterior**, que incluye la [corrección del encabezado de sesión](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Las compilaciones que incluyen el [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) restauran los encabezados de sesión de OpenCode. Esta corrección cubre la CLI, no la extensión de VS Code. Consulta el [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Clientes con problemas conocidos + +Estos clientes tienen una compatibilidad con sesiones ausente o incompleta en las versiones que +investigamos. Los informes enlazados permiten seguir las correcciones y las soluciones alternativas. + +| Cliente | Estado y seguimiento | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | La información de sesión llega por algunas rutas de modelos, pero falta en otras. Reconocemos su encabezado nativo; aún es necesario enviarlo a través de todos los adaptadores. [Discusión #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | La compatibilidad automática con el encabezado de sesión se solicitó en el [issue #334186 de VS Code](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | La compatibilidad automática con el encabezado de sesión se solicitó en el [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | El [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) tiene una corrección propuesta en el [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), que aún no se ha fusionado. | ## Límites de uso diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index ba966e91e092..84390f198a70 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -83,15 +83,44 @@ La liste des modèles peut changer au fur et à mesure que nous en testons et en ## Où puis-je l'utiliser ? -OpenCode Go est conçu pour être utilisé avec [OpenCode](https://opencode.ai) et d'autres agents de codage populaires qui génèrent des types de requêtes similaires. - -Le trafic est surveillé afin de détecter tout trafic abusif qui dégrade l'expérience des autres utilisateurs. - -Pour éviter que votre compte ne soit signalé, assurez-vous que l'outil que vous utilisez - -1\. ne génère pas de trafic abusif -2\. s'identifie correctement (pas d'agents utilisateur génériques) -3\. inclut l'en-tête `x-opencode-session` afin que nous puissions optimiser la mise en cache des prompts +OpenCode Go est conçu pour [OpenCode](https://opencode.ai) et d'autres agents de codage +qui produisent des types de requêtes similaires. Le trafic est surveillé afin de détecter les abus qui +dégradent l'expérience des autres utilisateurs. + +Votre client doit : + +1. Envoyer le trafic habituel d'un agent de codage +2. S'identifier avec son propre agent utilisateur, tel que `my-coding-agent/1.0`, plutôt + qu'avec le nom générique d'un SDK ou d'une bibliothèque HTTP. +3. Envoyer un ID de session stable dans `x-opencode-session` pour chaque conversation afin que nous puissions optimiser le routage et + la mise en cache des prompts. + +### Clients validés + +Outre OpenCode, le bon fonctionnement des clients suivants avec OpenCode Go a été +validé. Nous ne garantissons toutefois pas qu'ils continueront à fonctionner à l'avenir. + +| Client | Prise en charge des sessions | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Les builds contenant la [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) envoient l'en-tête sur les requêtes OpenCode principales et auxiliaires. Le correctif a été fusionné après la v0.21.0 ; cette version seule ne l'inclut pas. | +| **Claude Code** | Go reconnaît son en-tête de session natif. Aucun wrapper d'en-tête personnalisé n'est nécessaire. | +| **Codex** | Go reconnaît son en-tête de session natif. Certaines versions et configurations de proxy l'omettent encore ; conservez l'en-tête de session lors du transfert des requêtes. | +| **ZCode** | Go reconnaît son en-tête de session natif. Notre [demande concernant `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) reste ouverte, mais il n'est plus nécessaire d'envoyer spécifiquement cet en-tête. | +| **Pi** | Les builds actuels envoient les informations de session pour OpenCode. Mettez à jour les installations plus anciennes. | +| **jcode** | Passez à la version **v0.81.6 ou ultérieure**, qui inclut le [correctif de l'en-tête de session](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Les builds contenant la [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) rétablissent les en-têtes de session OpenCode. Ce correctif concerne la CLI, pas l'extension VS Code. Consultez l'[issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Clients connus pour poser problème + +Dans les versions que nous avons examinées, ces clients ne prennent pas en charge les sessions +ou ne les prennent en charge que partiellement. Les rapports associés permettent de suivre les correctifs et les solutions de contournement. + +| Client | État et suivi | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | Les informations de session sont présentes pour certains chemins de modèles, mais absentes pour d'autres. Nous reconnaissons son en-tête natif ; il reste à l'envoyer depuis tous les adaptateurs. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | La prise en charge automatique de l'en-tête de session fait l'objet de l'[issue VS Code #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | La prise en charge automatique de l'en-tête de session fait l'objet de l'[issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | L'[issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) dispose d'un correctif proposé dans la [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), qui n'a pas encore été fusionnée. | ## Limites d'utilisation diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index e4f142166113..eddc58f34837 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -91,15 +91,44 @@ L'elenco dei modelli potrebbe cambiare man mano che ne testiamo e aggiungiamo di ## Dove posso usarlo? -OpenCode Go è progettato per essere utilizzato con [OpenCode](https://opencode.ai) e altri agenti di programmazione popolari che generano tipi di richieste simili. - -Il traffico viene monitorato per rilevare traffico abusivo che compromette l'esperienza degli altri utenti. - -Per evitare che il tuo account venga segnalato, assicurati che lo strumento che utilizzi - -1\. non generi traffico abusivo -2\. si identifichi correttamente (senza user agent generici) -3\. includa l'header `x-opencode-session` in modo da consentirci di ottimizzare il caching dei prompt +OpenCode Go è progettato per [OpenCode](https://opencode.ai) e altri agenti di programmazione +che producono tipi di richieste simili. Il traffico viene monitorato per rilevare gli abusi che +compromettono l'esperienza degli altri utenti. + +Il tuo client deve: + +1. Inviare il traffico tipico di un agente di programmazione +2. Identificarsi con un proprio user agent, ad esempio `my-coding-agent/1.0`, anziché + con il nome generico di un SDK o di una libreria HTTP. +3. Inviare un ID di sessione stabile in `x-opencode-session` per ogni conversazione, in modo da consentirci di ottimizzare il routing e + il caching dei prompt. + +### Client convalidati + +Oltre a OpenCode, è stato verificato che i seguenti client funzionino correttamente +con OpenCode Go. Tuttavia, non garantiamo che continueranno a funzionare in futuro. + +| Client | Supporto delle sessioni | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Le build contenenti la [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) inviano l'header nelle richieste OpenCode principali e ausiliarie. La correzione è stata unita dopo la v0.21.0; quella versione da sola non la include. | +| **Claude Code** | Go riconosce il suo header di sessione nativo. Non è necessario alcun wrapper per header personalizzati. | +| **Codex** | Go riconosce il suo header di sessione nativo. Alcune versioni e configurazioni proxy continuano a ometterlo; mantieni l'header di sessione quando inoltri le richieste. | +| **ZCode** | Go riconosce il suo header di sessione nativo. La nostra [richiesta per `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) è ancora aperta, ma non è più necessario inviare proprio quell'header. | +| **Pi** | Le build attuali inviano le informazioni di sessione per OpenCode. Aggiorna le installazioni meno recenti. | +| **jcode** | Aggiorna alla versione **v0.81.6 o successiva**, che include la [correzione dell'header di sessione](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Le build contenenti la [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) ripristinano gli header di sessione OpenCode. Questa correzione riguarda la CLI, non l'estensione VS Code. Consulta l'[issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Client con problemi noti + +Nelle versioni che abbiamo esaminato, questi client non supportano le sessioni +o le supportano solo parzialmente. Le segnalazioni collegate consentono di seguire le correzioni e le soluzioni alternative. + +| Client | Stato e segnalazioni | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | Le informazioni di sessione arrivano tramite alcuni percorsi dei modelli, ma mancano in altri. Riconosciamo il suo header nativo; resta da inviarlo attraverso tutti gli adapter. [Discussione #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Il supporto automatico dell'header di sessione è stato richiesto nell'[issue #334186 di VS Code](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Il supporto automatico dell'header di sessione è stato richiesto nell'[issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | L'[issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) ha una correzione proposta nella [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), che non è stata ancora unita. | ## Limiti di utilizzo diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index 21f16312eb6d..d0f2207273ff 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -83,15 +83,41 @@ OpenCode Goをサブスクライブできるのは、1つのワークスペー ## どこで使用できますか? -OpenCode Goは、[OpenCode](https://opencode.ai)や、同様の種類のリクエストを生成するその他の一般的なコーディングエージェントで使用することを想定しています。 +OpenCode Goは、[OpenCode](https://opencode.ai)および同様の種類のリクエストを生成するその他のコーディングエージェント向けに設計されています。 +他のユーザーの利用体験を損なう不正利用がないか、トラフィックを監視しています。 -他のユーザーの利用体験を損なう不正なトラフィックがないか監視されています。 +クライアントは以下の要件を満たす必要があります。 -アカウントにフラグが付けられないよう、使用するツールが以下の条件を満たしていることを確認してください。 +1. 一般的なコーディングエージェントのトラフィックを送信する。 +2. 汎用的なSDK名やHTTPライブラリ名ではなく、`my-coding-agent/1.0`のような独自のユーザーエージェントで自身を識別する。 +3. ルーティングとプロンプトキャッシュを最適化できるよう、会話ごとに安定したセッションIDを`x-opencode-session`で送信する。 -1\. 不正なトラフィックを生成しない -2\. 自身を適切に識別する(汎用的すぎるユーザーエージェントを使用しない) -3\. プロンプトキャッシュを最適化できるよう、`x-opencode-session`ヘッダーを含める +### 動作確認済みのクライアント + +OpenCodeに加えて、以下のクライアントがOpenCode Goで正常に動作することを確認しています。 +ただし、今後も動作し続けることを保証するものではありません。 + +| クライアント | セッション対応 | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864)を含むビルドでは、OpenCodeのメインリクエストと補助リクエストの両方でヘッダーが送信されます。この修正はv0.21.0より後にマージされたため、v0.21.0自体には含まれていません。 | +| **Claude Code** | GoはClaude Code固有のセッションヘッダーを認識します。カスタムヘッダーを追加するラッパーは不要です。 | +| **Codex** | GoはCodex固有のセッションヘッダーを認識します。一部のバージョンやプロキシ設定では引き続きヘッダーが省略されるため、リクエストを転送するときはセッションヘッダーを保持してください。 | +| **ZCode** | GoはZCode固有のセッションヘッダーを認識します。[`x-opencode-session`の追加リクエスト](https://github.com/zai-org/feedback/issues/492)は未解決ですが、この特定のヘッダーを送信する必要はなくなりました。 | +| **Pi** | 現在のビルドはOpenCodeにセッション情報を送信します。古いインストールは更新してください。 | +| **jcode** | [セッションヘッダーの修正](https://github.com/1jehuang/jcode/issues/1167)を含む**v0.81.6以降**に更新してください。 | +| **Kilo Code CLI** | [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752)を含むビルドでは、OpenCodeのセッションヘッダーが再び送信されます。この修正の対象はCLIであり、VS Code拡張機能ではありません。[issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723)を参照してください。 | + +### 既知の問題があるクライアント + +調査したバージョンでは、以下のクライアントのセッション対応が欠けているか不完全です。 +リンク先の報告で修正や回避策を追跡しています。 + +| クライアント | 状況と追跡 | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | 一部のモデルパスではセッション情報が届きますが、他のパスでは欠落します。GoはDeepSeek Harness固有のヘッダーを認識します。残る対応は、すべてのアダプターからそのヘッダーを送信することです。[Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495)。 | +| **GitHub Copilot Chat** | セッションヘッダーの自動送信対応は[VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186)で要望されています。 | +| **Kimi Code** | セッションヘッダーの自動送信対応は[issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506)で要望されています。 | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317)には[PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327)で修正案がありますが、まだマージされていません。 | ## 利用制限 diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index 28f52fdf6d9e..e3989dca50fb 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -83,15 +83,41 @@ workspace당 한 명의 멤버만 OpenCode Go를 구독할 수 있습니다. ## 어디에서 사용할 수 있나요? -OpenCode Go는 [OpenCode](https://opencode.ai) 및 유사한 유형의 요청을 생성하는 다른 인기 코딩 에이전트와 함께 사용하도록 설계되었습니다. +OpenCode Go는 [OpenCode](https://opencode.ai) 및 유사한 유형의 요청을 생성하는 다른 코딩 에이전트용으로 설계되었습니다. +다른 사용자의 이용 경험을 저해하는 악용이 있는지 트래픽을 모니터링합니다. -다른 사용자의 이용 경험을 저해하는 악성 트래픽이 있는지 모니터링합니다. +클라이언트는 다음 요건을 충족해야 합니다. -계정에 플래그가 지정되지 않도록 사용 중인 도구가 다음 조건을 충족하는지 확인하세요. +1. 일반적인 코딩 에이전트 트래픽을 전송합니다. +2. 일반적인 SDK 또는 HTTP 라이브러리 이름이 아닌 `my-coding-agent/1.0`과 같은 자체 user agent로 식별합니다. +3. 라우팅과 프롬프트 캐싱을 최적화할 수 있도록 각 대화에서 안정적인 세션 ID를 `x-opencode-session`으로 전송합니다. -1\. 악성 트래픽을 생성하지 않음 -2\. 자체 정보를 올바르게 표시함(포괄적인 사용자 에이전트를 사용하지 않음) -3\. 프롬프트 캐싱을 최적화할 수 있도록 `x-opencode-session` 헤더를 포함함 +### 검증된 클라이언트 + +OpenCode 외에도 다음 클라이언트가 OpenCode Go에서 올바르게 작동하는 것으로 검증되었습니다. +다만 앞으로도 계속 작동할 것이라고 보장하지는 않습니다. + +| 클라이언트 | 세션 지원 | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864)가 포함된 빌드는 OpenCode의 기본 요청과 보조 요청 모두에서 헤더를 전송합니다. 이 수정은 v0.21.0 이후에 병합되었으므로 v0.21.0 릴리스 자체에는 포함되지 않습니다. | +| **Claude Code** | Go는 Claude Code의 고유 세션 헤더를 인식합니다. 사용자 지정 헤더 래퍼가 필요하지 않습니다. | +| **Codex** | Go는 Codex의 고유 세션 헤더를 인식합니다. 일부 버전과 프록시 설정에서는 여전히 이 헤더가 누락되므로 요청을 전달할 때 세션 헤더를 유지하세요. | +| **ZCode** | Go는 ZCode의 고유 세션 헤더를 인식합니다. [`x-opencode-session` 요청](https://github.com/zai-org/feedback/issues/492)은 아직 열려 있지만 이제 해당 헤더를 별도로 전송할 필요는 없습니다. | +| **Pi** | 현재 빌드는 OpenCode에 세션 정보를 전송합니다. 이전 설치 버전은 업데이트하세요. | +| **jcode** | [세션 헤더 수정](https://github.com/1jehuang/jcode/issues/1167)이 포함된 **v0.81.6 이상**으로 업데이트하세요. | +| **Kilo Code CLI** | [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752)가 포함된 빌드는 OpenCode 세션 헤더를 복원합니다. 이 수정은 CLI에만 적용되며 VS Code 확장에는 적용되지 않습니다. [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723)을 참조하세요. | + +### 알려진 문제가 있는 클라이언트 + +조사한 버전에서 다음 클라이언트는 세션 지원이 없거나 불완전했습니다. +링크된 보고서에서 수정 사항과 해결 방법을 추적합니다. + +| 클라이언트 | 상태 및 추적 | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | 일부 모델 경로에서는 세션 정보가 전달되지만 다른 경로에서는 누락됩니다. Go는 DeepSeek Harness의 고유 헤더를 인식하며, 남은 작업은 모든 어댑터에서 이 헤더를 전송하는 것입니다. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | 자동 세션 헤더 지원은 [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186)에서 요청되었습니다. | +| **Kimi Code** | 자동 세션 헤더 지원은 [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506)에서 요청되었습니다. | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317)에 [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327)로 수정안이 제안되었지만 아직 병합되지 않았습니다. | ## 사용 한도 diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index 39034c9250e2..fa8299900bef 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -93,15 +93,44 @@ Listen over modeller kan endres etter hvert som vi tester og legger til nye. ## Hvor kan jeg bruke det? -OpenCode Go er utviklet for bruk med [OpenCode](https://opencode.ai) og andre populære kodeagenter som genererer lignende typer forespørsler. - -Trafikken overvåkes for misbruk som forringer opplevelsen for andre brukere. - -For å sikre at kontoen din ikke blir flagget, må du sørge for at verktøyet du bruker, - -1\. ikke genererer misbrukstrafikk -2\. identifiserer seg korrekt (ingen generiske User-Agent-identifikatorer) -3\. inkluderer `x-opencode-session`-headeren, slik at vi kan optimalisere promptbufring +OpenCode Go er utviklet for [OpenCode](https://opencode.ai) og andre kodeagenter +som sender lignende typer forespørsler. Trafikken overvåkes for misbruk som +forringer opplevelsen for andre brukere. + +Klienten din skal: + +1. Sende typisk trafikk fra en kodeagent. +2. Identifisere seg med sin egen user agent, for eksempel `my-coding-agent/1.0`, i stedet + for et generisk navn på et SDK eller HTTP-bibliotek. +3. Sende en stabil sesjons-ID i `x-opencode-session` for hver samtale, slik at vi kan optimalisere ruting og + promptbufring. + +### Validerte klienter + +I tillegg til OpenCode er følgende klienter validert for å fungere riktig +med OpenCode Go. Vi garanterer imidlertid ikke at de vil fortsette å fungere i fremtiden. + +| Klient | Sesjonsstøtte | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Bygg som inneholder [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864), sender headeren i primære og sekundære OpenCode-forespørsler. Rettelsen ble slått sammen etter v0.21.0; den versjonen inneholder den ikke alene. | +| **Claude Code** | Go gjenkjenner den innebygde sesjonsheaderen. En wrapper for en egendefinert header er ikke nødvendig. | +| **Codex** | Go gjenkjenner den innebygde sesjonsheaderen. Noen versjoner og proxyoppsett utelater den fortsatt; behold sesjonsheaderen når forespørsler videresendes. | +| **ZCode** | Go gjenkjenner den innebygde sesjonsheaderen. Vår [forespørsel om `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) er fortsatt åpen, men det er ikke lenger nødvendig å sende akkurat denne headeren. | +| **Pi** | Gjeldende bygg sender sesjonsinformasjon for OpenCode. Oppdater eldre installasjoner. | +| **jcode** | Oppdater til **v0.81.6 eller nyere**, som inneholder [rettelsen for sesjonsheaderen](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Bygg som inneholder [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752), gjenoppretter OpenCode-sesjonsheadere. Denne rettelsen dekker CLI-en, ikke VS Code-utvidelsen. Se [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Kjente problematiske klienter + +Disse klientene mangler sesjonsstøtte eller har ufullstendig støtte i versjonene vi +undersøkte. De lenkede rapportene følger rettelser og midlertidige løsninger. + +| Klient | Status og oppfølging | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **DeepSeek Harness** | Sesjonsinformasjon mottas på noen modellbaner, men mangler på andre. Vi gjenkjenner den innebygde headeren; det gjenstående arbeidet er å sende den gjennom alle adaptere. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Automatisk støtte for sesjonsheaderen er etterspurt i [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Automatisk støtte for sesjonsheaderen er etterspurt i [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) har en foreslått rettelse i [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), som ennå ikke er slått sammen. | ## Bruksgrenser diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index a9c94fc30843..34737509154e 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -87,15 +87,44 @@ Lista modeli może ulec zmianie w miarę testowania i dodawania nowych. ## Gdzie można z tego korzystać? -OpenCode Go jest przeznaczony do użytku z [OpenCode](https://opencode.ai) i innymi popularnymi agentami kodującymi, którzy generują podobne rodzaje żądań. - -Ruch jest monitorowany pod kątem nadużyć, które pogarszają komfort korzystania z usługi przez innych użytkowników. - -Aby Twoje konto nie zostało oznaczone, upewnij się, że używane przez Ciebie narzędzie - -1\. nie generuje ruchu stanowiącego nadużycie -2\. prawidłowo się identyfikuje (bez ogólnych identyfikatorów User-Agent) -3\. zawiera nagłówek `x-opencode-session`, abyśmy mogli zoptymalizować buforowanie promptów +OpenCode Go jest przeznaczony dla [OpenCode](https://opencode.ai) i innych agentów kodujących, +którzy wysyłają podobne rodzaje żądań. Ruch jest monitorowany pod kątem nadużyć, +które pogarszają komfort korzystania z usługi przez innych użytkowników. + +Twój klient powinien: + +1. Wysyłać ruch typowy dla agenta kodującego. +2. Identyfikować się za pomocą własnego user agenta, na przykład `my-coding-agent/1.0`, zamiast + ogólnej nazwy SDK lub biblioteki HTTP. +3. Wysyłać stabilny identyfikator sesji w nagłówku `x-opencode-session` dla każdej rozmowy, aby umożliwić optymalizację routingu i + buforowania promptów. + +### Zweryfikowane klienty + +Poza OpenCode zweryfikowano, że poniżsi klienci działają prawidłowo +z OpenCode Go. Nie gwarantujemy jednak, że będą nadal działać w przyszłości. + +| Klient | Obsługa sesji | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Kompilacje zawierające [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) wysyłają nagłówek w głównych i pomocniczych żądaniach OpenCode. Poprawkę scalono po wersji v0.21.0; samo to wydanie jej nie zawiera. | +| **Claude Code** | Go rozpoznaje jego natywny nagłówek sesji. Wrapper dodający niestandardowy nagłówek nie jest potrzebny. | +| **Codex** | Go rozpoznaje jego natywny nagłówek sesji. Niektóre wersje i konfiguracje proxy nadal go pomijają; podczas przekazywania żądań należy zachować nagłówek sesji. | +| **ZCode** | Go rozpoznaje jego natywny nagłówek sesji. Nasza [prośba o `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) pozostaje otwarta, ale wysyłanie tego konkretnego nagłówka nie jest już konieczne. | +| **Pi** | Aktualne kompilacje wysyłają informacje o sesji dla OpenCode. Zaktualizuj starsze instalacje. | +| **jcode** | Zaktualizuj do wersji **v0.81.6 lub nowszej**, która zawiera [poprawkę nagłówka sesji](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Kompilacje zawierające [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) przywracają nagłówki sesji OpenCode. Ta poprawka obejmuje CLI, ale nie rozszerzenie VS Code. Zobacz [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Klienty ze znanymi problemami + +W badanych przez nas wersjach tych klientów obsługa sesji jest niepełna lub jej brakuje. +Raporty pod podanymi linkami służą do śledzenia poprawek i obejść. + +| Klient | Stan i śledzenie | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | Informacje o sesji docierają w niektórych ścieżkach modeli, ale brakuje ich w innych. Rozpoznajemy jego natywny nagłówek; pozostaje zapewnić jego wysyłanie przez wszystkie adaptery. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Prośbę o automatyczną obsługę nagłówka sesji zgłoszono w [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Prośbę o automatyczną obsługę nagłówka sesji zgłoszono w [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | Dla [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) zaproponowano poprawkę w [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), który nie został jeszcze scalony. | ## Limity użycia diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index c8b621a007e9..0083a3a0677c 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -93,15 +93,44 @@ A lista de modelos pode mudar conforme testamos e adicionamos novos. ## Onde posso usá-lo? -O OpenCode Go foi projetado para ser usado com o [OpenCode](https://opencode.ai) e outros agentes de programação populares que geram tipos de requisições semelhantes. - -O tráfego é monitorado para detectar tráfego abusivo que prejudique a experiência de outros usuários. - -Para evitar que sua conta seja sinalizada, verifique se a ferramenta que você está usando - -1\. não gera tráfego abusivo -2\. se identifica corretamente (sem user agents genéricos) -3\. inclui o header `x-opencode-session` para que possamos otimizar o cache de prompts +O OpenCode Go foi projetado para o [OpenCode](https://opencode.ai) e outros agentes de programação +que produzem tipos semelhantes de requisições. O tráfego é monitorado para detectar abusos que +prejudiquem a experiência de outros usuários. + +Seu cliente deve: + +1. Enviar o tráfego típico de um agente de programação +2. Identificar-se com seu próprio user agent, como `my-coding-agent/1.0`, em vez + do nome genérico de um SDK ou de uma biblioteca HTTP. +3. Enviar um ID de sessão estável em `x-opencode-session` para cada conversa, para que possamos otimizar o roteamento e + o cache de prompts. + +### Clientes validados + +Além do OpenCode, os clientes a seguir foram validados para funcionar corretamente +com o OpenCode Go. No entanto, não garantimos que continuarão funcionando no futuro. + +| Cliente | Suporte a sessões | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Hermes** | As builds que contêm o [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) enviam o header nas requisições principais e auxiliares do OpenCode. A correção foi incorporada após a v0.21.0; essa versão por si só não a inclui. | +| **Claude Code** | O Go reconhece seu header de sessão nativo. Não é necessário usar um wrapper para headers personalizados. | +| **Codex** | O Go reconhece seu header de sessão nativo. Algumas versões e configurações de proxy ainda o omitem; preserve o header de sessão ao encaminhar requisições. | +| **ZCode** | O Go reconhece seu header de sessão nativo. Nossa [solicitação de `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) continua aberta, mas não é mais necessário enviar especificamente esse header. | +| **Pi** | As builds atuais enviam informações de sessão para o OpenCode. Atualize instalações mais antigas. | +| **jcode** | Atualize para a versão **v0.81.6 ou posterior**, que inclui a [correção do header de sessão](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | As builds que contêm o [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) restauram os headers de sessão do OpenCode. Essa correção abrange a CLI, não a extensão do VS Code. Consulte a [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Clientes com problemas conhecidos + +Esses clientes não têm suporte a sessões ou apresentam suporte incompleto nas versões que +investigamos. Os relatórios vinculados acompanham correções e soluções alternativas. + +| Cliente | Status e acompanhamento | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **DeepSeek Harness** | As informações de sessão chegam por alguns caminhos de modelos, mas não por outros. Reconhecemos seu header nativo; ainda é necessário enviá-lo por todos os adaptadores. [Discussão #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | O suporte automático ao header de sessão foi solicitado na [issue #334186 do VS Code](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | O suporte automático ao header de sessão foi solicitado na [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | A [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) tem uma correção proposta no [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), que ainda não foi incorporada. | ## Limites de uso diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index c7e250902d15..f56cfbb125ce 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -93,15 +93,44 @@ OpenCode Go работает так же, как и любой другой пр ## Где можно использовать OpenCode Go? -OpenCode Go предназначен для использования с [OpenCode](https://opencode.ai) и другими популярными агентами для программирования, которые создают запросы схожих типов. - -Трафик отслеживается для выявления злоупотреблений, ухудшающих работу сервиса для других пользователей. - -Чтобы ваша учетная запись не была отмечена, убедитесь, что используемый вами инструмент - -1\. не создает трафик, представляющий собой злоупотребление -2\. правильно идентифицирует себя (без универсальных значений User-Agent) -3\. включает заголовок `x-opencode-session`, чтобы мы могли оптимизировать кеширование промптов +OpenCode Go предназначен для [OpenCode](https://opencode.ai) и других агентов для программирования, +которые отправляют запросы схожих типов. Трафик отслеживается для выявления злоупотреблений, +ухудшающих работу сервиса для других пользователей. + +Ваш клиент должен: + +1. Отправлять трафик, типичный для агента программирования. +2. Идентифицировать себя с помощью собственного user agent, например `my-coding-agent/1.0`, а не + универсального названия SDK или HTTP-библиотеки. +3. Отправлять стабильный идентификатор сессии в заголовке `x-opencode-session` для каждого диалога, чтобы мы могли оптимизировать маршрутизацию и + кеширование промптов. + +### Проверенные клиенты + +Помимо OpenCode, корректная работа с OpenCode Go подтверждена для следующих клиентов. +Однако мы не гарантируем, что они продолжат работать в будущем. + +| Клиент | Поддержка сессий | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Сборки, содержащие [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864), отправляют заголовок в основных и вспомогательных запросах OpenCode. Исправление было объединено после v0.21.0; само это издание его не содержит. | +| **Claude Code** | Go распознает его нативный заголовок сессии. Обертка для добавления пользовательского заголовка не требуется. | +| **Codex** | Go распознает его нативный заголовок сессии. Некоторые версии и конфигурации прокси по-прежнему его не передают; сохраняйте заголовок сессии при пересылке запросов. | +| **ZCode** | Go распознает его нативный заголовок сессии. Наш [запрос на `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) остается открытым, но отправлять именно этот заголовок больше не требуется. | +| **Pi** | Текущие сборки отправляют информацию о сессии для OpenCode. Обновите более старые установки. | +| **jcode** | Обновитесь до версии **v0.81.6 или новее**, которая содержит [исправление заголовка сессии](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Сборки, содержащие [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752), восстанавливают заголовки сессии OpenCode. Это исправление относится к CLI, но не к расширению VS Code. См. [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Клиенты с известными проблемами + +В исследованных нами версиях этих клиентов поддержка сессий отсутствует или реализована +не полностью. По ссылкам можно отслеживать исправления и обходные решения. + +| Клиент | Статус и отслеживание | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | Информация о сессии поступает для некоторых путей моделей, но отсутствует для других. Мы распознаем его нативный заголовок; остается обеспечить его отправку через все адаптеры. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Запрос на автоматическую поддержку заголовка сессии создан в [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Запрос на автоматическую поддержку заголовка сессии создан в [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | Для [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) предложено исправление в [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), который еще не объединен. | ## Лимиты использования diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index 08a1026ba012..621599d46f3a 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -83,15 +83,41 @@ OpenCode Go ทำงานเหมือนกับผู้ให้บร ## ใช้งานได้ที่ไหน? -OpenCode Go ออกแบบมาเพื่อใช้กับ [OpenCode](https://opencode.ai) และเอเจนต์เขียนโค้ดยอดนิยมอื่นๆ ที่สร้างคำขอในลักษณะเดียวกัน +OpenCode Go ออกแบบมาสำหรับ [OpenCode](https://opencode.ai) และ coding agent อื่นๆ ที่สร้างคำขอประเภทเดียวกัน +ระบบจะตรวจสอบทราฟฟิกเพื่อหาการใช้งานในทางที่ผิดซึ่งทำให้ประสบการณ์ของผู้ใช้รายอื่นแย่ลง -ระบบจะตรวจสอบการรับส่งข้อมูลเพื่อค้นหาการใช้งานในทางที่ผิดซึ่งส่งผลกระทบต่อประสบการณ์ของผู้ใช้รายอื่น +ไคลเอนต์ของคุณควร: -เพื่อให้แน่ใจว่าบัญชีของคุณจะไม่ถูกตั้งค่าสถานะ โปรดตรวจสอบว่าเครื่องมือที่คุณใช้ +1. ส่งทราฟฟิกตามปกติของ coding agent +2. ระบุตัวตนด้วย user agent ของตนเอง เช่น `my-coding-agent/1.0` แทนชื่อ SDK หรือไลบรารี HTTP แบบทั่วไป +3. ส่ง session ID ที่คงที่ใน `x-opencode-session` สำหรับแต่ละบทสนทนา เพื่อให้เราปรับ routing และ prompt caching ให้เหมาะสมได้ -1\. ไม่สร้างการรับส่งข้อมูลที่เป็นการใช้งานในทางที่ผิด -2\. ระบุตัวตนอย่างถูกต้อง (ไม่ใช้ข้อมูลระบุตัวแทนผู้ใช้แบบกว้างเกินไป) -3\. มีส่วนหัว `x-opencode-session` เพื่อให้เราสามารถปรับการแคชพรอมต์ให้เหมาะสมได้ +### ไคลเอนต์ที่ผ่านการตรวจสอบแล้ว + +นอกจาก OpenCode แล้ว ไคลเอนต์ต่อไปนี้ได้รับการตรวจสอบแล้วว่าทำงานกับ OpenCode Go ได้อย่างถูกต้อง +อย่างไรก็ตาม เราไม่รับประกันว่าไคลเอนต์เหล่านี้จะยังคงทำงานได้ต่อไปในอนาคต + +| ไคลเอนต์ | การรองรับเซสชัน | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Hermes** | บิลด์ที่มี [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) จะส่งส่วนหัวในคำขอ OpenCode ทั้งคำขอหลักและคำขอเสริม การแก้ไขนี้ถูก merge หลัง v0.21.0 ดังนั้นตัวรีลีส v0.21.0 เองจึงยังไม่มีการแก้ไขนี้ | +| **Claude Code** | Go รู้จักส่วนหัวเซสชันแบบ native ของ Claude Code จึงไม่ต้องใช้ wrapper เพื่อเพิ่มส่วนหัวแบบกำหนดเอง | +| **Codex** | Go รู้จักส่วนหัวเซสชันแบบ native ของ Codex แต่บางเวอร์ชันและการตั้งค่า proxy ยังไม่ส่งส่วนหัวนี้ โปรดคงส่วนหัวเซสชันไว้เมื่อ forward คำขอ | +| **ZCode** | Go รู้จักส่วนหัวเซสชันแบบ native ของ ZCode [คำขอให้เพิ่ม `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) ของเรายังเปิดอยู่ แต่ไม่จำเป็นต้องส่งส่วนหัวเฉพาะนี้อีกต่อไป | +| **Pi** | บิลด์ปัจจุบันส่งข้อมูลเซสชันสำหรับ OpenCode โปรดอัปเดตการติดตั้งเวอร์ชันเก่า | +| **jcode** | อัปเดตเป็น **v0.81.6 หรือใหม่กว่า** ซึ่งมี[การแก้ไขส่วนหัวเซสชัน](https://github.com/1jehuang/jcode/issues/1167) | +| **Kilo Code CLI** | บิลด์ที่มี [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) จะทำให้ส่วนหัวเซสชันของ OpenCode กลับมาทำงาน การแก้ไขนี้ครอบคลุม CLI แต่ไม่ครอบคลุมส่วนขยาย VS Code ดู [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723) | + +### ไคลเอนต์ที่ทราบว่ามีปัญหา + +ไคลเอนต์เหล่านี้ไม่รองรับเซสชันหรือรองรับไม่สมบูรณ์ในเวอร์ชันที่เราตรวจสอบ +รายงานที่ลิงก์ไว้ใช้ติดตามการแก้ไขและวิธีแก้ปัญหาชั่วคราว + +| ไคลเอนต์ | สถานะและการติดตาม | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | ข้อมูลเซสชันส่งมาถึงใน model path บางรายการ แต่ขาดหายไปในรายการอื่น Go รู้จักส่วนหัวแบบ native ของ DeepSeek Harness งานที่เหลือคือการส่งส่วนหัวนี้ผ่าน adapter ทั้งหมด [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495) | +| **GitHub Copilot Chat** | มีการขอให้รองรับส่วนหัวเซสชันโดยอัตโนมัติใน [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) | +| **Kimi Code** | มีการขอให้รองรับส่วนหัวเซสชันโดยอัตโนมัติใน [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) มีการเสนอการแก้ไขใน [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327) ซึ่งยังไม่ได้ merge | ## Usage limits diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index 1b828eb12b85..0cd0cd0220f0 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -83,15 +83,41 @@ Test edip yenilerini ekledikçe model listesi değişebilir. ## Nerede kullanabilirim? -OpenCode Go, [OpenCode](https://opencode.ai) ve benzer türde istekler üreten diğer popüler kodlama aracılarıyla kullanılmak üzere tasarlanmıştır. +OpenCode Go, [OpenCode](https://opencode.ai) ve benzer türde istekler üreten diğer kodlama aracıları için tasarlanmıştır. +Trafik, diğer kullanıcıların deneyimini olumsuz etkileyen kötüye kullanımlara karşı izlenir. -Trafik, diğer kullanıcıların deneyimini olumsuz etkileyen kötüye kullanım amaçlı trafiğe karşı izlenir. +İstemciniz şunları yapmalıdır: -Hesabınızın işaretlenmemesi için kullandığınız aracın +1. Tipik kodlama aracısı trafiği göndermelidir. +2. Genel bir SDK veya HTTP kitaplığı adı yerine `my-coding-agent/1.0` gibi kendine ait bir user agent ile kendini tanıtmalıdır. +3. Yönlendirmeyi ve istem önbelleğe almayı optimize edebilmemiz için her konuşmada `x-opencode-session` içinde değişmeyen bir oturum kimliği göndermelidir. -1\. kötüye kullanım amaçlı trafik oluşturmadığından -2\. kendisini doğru şekilde tanıttığından (genel kapsamlı kullanıcı aracıları kullanmadığından) -3\. istem önbelleğe almayı optimize edebilmemiz için `x-opencode-session` başlığını içerdiğinden emin olun +### Doğrulanmış İstemciler + +OpenCode'un yanı sıra aşağıdaki istemcilerin OpenCode Go ile düzgün çalıştığı doğrulanmıştır. +Ancak gelecekte de çalışmaya devam edeceklerini garanti etmiyoruz. + +| İstemci | Oturum desteği | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) içeren derlemeler, ana ve yardımcı OpenCode isteklerinde başlığı gönderir. Düzeltme v0.21.0'dan sonra birleştirilmiştir; dolayısıyla v0.21.0 sürümü tek başına bu düzeltmeyi içermez. | +| **Claude Code** | Go, Claude Code'un yerel oturum başlığını tanır. Özel başlık ekleyen bir sarmalayıcı gerekmez. | +| **Codex** | Go, Codex'in yerel oturum başlığını tanır. Bazı sürümler ve proxy yapılandırmaları hâlâ bu başlığı göndermemektedir; istekleri iletirken oturum başlığını koruyun. | +| **ZCode** | Go, ZCode'un yerel oturum başlığını tanır. [`x-opencode-session` talebimiz](https://github.com/zai-org/feedback/issues/492) hâlâ açıktır ancak artık özellikle bu başlığın gönderilmesi gerekli değildir. | +| **Pi** | Güncel derlemeler OpenCode için oturum bilgilerini gönderir. Eski kurulumları güncelleyin. | +| **jcode** | [Oturum başlığı düzeltmesini](https://github.com/1jehuang/jcode/issues/1167) içeren **v0.81.6 veya sonraki bir sürüme** güncelleyin. | +| **Kilo Code CLI** | [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) içeren derlemeler OpenCode oturum başlıklarını geri getirir. Bu düzeltme CLI'ı kapsar, VS Code uzantısını kapsamaz. Bkz. [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | + +### Sorunlu Olduğu Bilinen İstemciler + +Bu istemcilerin incelediğimiz sürümlerinde oturum desteği eksik veya tamamlanmamıştır. +Bağlantı verilen bildirimlerden düzeltmeleri ve geçici çözümleri takip edebilirsiniz. + +| İstemci | Durum ve takip | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **DeepSeek Harness** | Oturum bilgileri bazı model yollarında ulaşırken diğerlerinde eksiktir. Go, DeepSeek Harness'ın yerel başlığını tanır; yapılması gereken, bu başlığın tüm adaptörlerden gönderilmesidir. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Otomatik oturum başlığı desteği [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) içinde talep edilmiştir. | +| **Kimi Code** | Otomatik oturum başlığı desteği [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) içinde talep edilmiştir. | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) için [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327) içinde bir düzeltme önerilmiştir ancak henüz birleştirilmemiştir. | ## Kullanım limitleri diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index 58d11c9cc6ec..bbf58a721b5f 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -83,15 +83,40 @@ OpenCode Go 的工作方式与 OpenCode 中的其他提供商一样。 ## 可以在哪里使用? -OpenCode Go 适用于 [OpenCode](https://opencode.ai) 以及其他会产生类似请求的主流编程 Agent。 +OpenCode Go 适用于 [OpenCode](https://opencode.ai) 以及其他会产生类似请求的编程 Agent。 我们会监控流量,以识别影响其他用户体验的滥用行为。 -为避免你的账户被标记为异常,请确保你使用的工具: +你的客户端应当: -1\. 不产生滥用流量 -2\. 明确标识自身(不要使用过于笼统的 user agent 标识) -3\. 包含 `x-opencode-session` 请求头,以便我们优化提示词缓存 +1. 发送典型的编程 Agent 流量。 +2. 使用自身专属的 user agent 标识(例如 `my-coding-agent/1.0`),而不是通用的 SDK 或 HTTP 库名称。 +3. 为每段对话在 `x-opencode-session` 请求头中发送稳定的会话 ID,以便我们优化路由和提示词缓存。 + +### 已验证的客户端 + +除 OpenCode 外,以下客户端已通过验证,能够正常使用 OpenCode Go。但我们无法保证它们未来仍能正常使用。 + +| 客户端 | 会话支持 | +| --- | --- | +| **Hermes** | 包含 [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) 的构建版本会在主要和辅助 OpenCode 请求中发送该请求头。此修复在 v0.21.0 发布后才合并,因此 v0.21.0 本身并不包含该修复。 | +| **Claude Code** | Go 能识别其原生会话请求头,无需额外封装来添加自定义请求头。 | +| **Codex** | Go 能识别其原生会话请求头。某些版本和代理配置仍会遗漏该请求头;转发请求时请保留会话请求头。 | +| **ZCode** | Go 能识别其原生会话请求头。我们[请求支持 `x-opencode-session` 的 issue](https://github.com/zai-org/feedback/issues/492) 仍处于开放状态,但已不再需要发送这一特定请求头。 | +| **Pi** | 当前构建版本会为 OpenCode 发送会话信息。请更新旧版安装。 | +| **jcode** | 请更新至 **v0.81.6 或更高版本**,其中包含[会话请求头修复](https://github.com/1jehuang/jcode/issues/1167)。 | +| **Kilo Code CLI** | 包含 [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) 的构建版本恢复了 OpenCode 会话请求头。此修复仅适用于 CLI,不适用于 VS Code 扩展。参见 [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723)。 | + +### 已知存在问题的客户端 + +在我们调查的版本中,以下客户端缺少会话支持,或支持不完整。相关报告链接可用于跟踪修复进展和临时解决方案。 + +| 客户端 | 状态与跟踪 | +| --- | --- | +| **DeepSeek Harness** | 某些模型调用路径会传递会话信息,但其他路径中缺失。我们能识别其原生请求头;剩余工作是在所有适配器中发送该请求头。参见[讨论 #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495)。 | +| **GitHub Copilot Chat** | [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) 已提出自动发送会话请求头的支持请求。 | +| **Kimi Code** | [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) 已提出自动发送会话请求头的支持请求。 | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) 已有拟议修复 [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327),但尚未合并。 | ## 使用限制 diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index e9f10688f6f5..4cd2b8a9fd0b 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -83,15 +83,40 @@ OpenCode Go 的運作方式與 OpenCode 中的任何其他供應商相同。 ## 可以在哪裡使用? -OpenCode Go 適用於 [OpenCode](https://opencode.ai) 以及其他會產生類似請求的主流程式設計 Agent。 +OpenCode Go 適用於 [OpenCode](https://opencode.ai) 以及其他會產生類似請求的程式設計 Agent。 我們會監控流量,以識別影響其他使用者體驗的濫用行為。 -為避免您的帳戶被標記為異常,請確保您使用的工具: +您的用戶端應: -1\. 不產生濫用流量 -2\. 明確標識自身(不要使用過於籠統的 user agent 識別資訊) -3\. 包含 `x-opencode-session` 請求標頭,以便我們最佳化提示詞快取 +1. 傳送典型的程式設計 Agent 流量。 +2. 使用自身專屬的 user agent 識別資訊(例如 `my-coding-agent/1.0`),而非通用的 SDK 或 HTTP 函式庫名稱。 +3. 為每段對話在 `x-opencode-session` 請求標頭中傳送穩定的工作階段 ID,以便我們最佳化路由和提示詞快取。 + +### 已驗證的用戶端 + +除了 OpenCode,以下用戶端已通過驗證,可正常使用 OpenCode Go。但我們無法保證它們未來仍能正常使用。 + +| 用戶端 | 工作階段支援 | +| --- | --- | +| **Hermes** | 包含 [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) 的建置版本會在主要和輔助 OpenCode 請求中傳送該標頭。此修正是在 v0.21.0 發布後才合併,因此 v0.21.0 本身並未包含此修正。 | +| **Claude Code** | Go 可辨識其原生工作階段標頭,無須額外封裝來新增自訂標頭。 | +| **Codex** | Go 可辨識其原生工作階段標頭。部分版本和代理伺服器設定仍會遺漏該標頭;轉送請求時請保留工作階段標頭。 | +| **ZCode** | Go 可辨識其原生工作階段標頭。我們[請求支援 `x-opencode-session` 的 issue](https://github.com/zai-org/feedback/issues/492) 仍未關閉,但已不再需要傳送這個特定標頭。 | +| **Pi** | 目前的建置版本會為 OpenCode 傳送工作階段資訊。請更新舊版安裝。 | +| **jcode** | 請更新至 **v0.81.6 或更新版本**,其中包含[工作階段標頭修正](https://github.com/1jehuang/jcode/issues/1167)。 | +| **Kilo Code CLI** | 包含 [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) 的建置版本恢復了 OpenCode 工作階段標頭。此修正僅適用於 CLI,不適用於 VS Code 擴充套件。請參閱 [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723)。 | + +### 已知有問題的用戶端 + +在我們調查的版本中,以下用戶端缺少工作階段支援,或支援不完整。相關回報連結可用來追蹤修正進度與暫時解決方式。 + +| 用戶端 | 狀態與追蹤 | +| --- | --- | +| **DeepSeek Harness** | 部分模型呼叫路徑會傳遞工作階段資訊,但其他路徑中缺少這些資訊。我們可辨識其原生標頭;剩餘工作是在所有配接器中傳送該標頭。請參閱[討論 #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495)。 | +| **GitHub Copilot Chat** | [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) 已提出自動傳送工作階段標頭的支援請求。 | +| **Kimi Code** | [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) 已提出自動傳送工作階段標頭的支援請求。 | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) 已有修正提案 [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327),但尚未合併。 | ## 使用限制 From d6855b6b47a8433462ac6aeeba882ccf734cb7f1 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 8 Sep 2026 06:51:49 +0000 Subject: [PATCH 052/129] chore: generate --- packages/web/src/content/docs/ar/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/bs/go.mdx | 28 ++++++++++---------- packages/web/src/content/docs/da/go.mdx | 24 ++++++++--------- packages/web/src/content/docs/de/go.mdx | 28 ++++++++++---------- packages/web/src/content/docs/es/go.mdx | 28 ++++++++++---------- packages/web/src/content/docs/fr/go.mdx | 28 ++++++++++---------- packages/web/src/content/docs/it/go.mdx | 26 +++++++++---------- packages/web/src/content/docs/ja/go.mdx | 26 +++++++++---------- packages/web/src/content/docs/ko/go.mdx | 24 ++++++++--------- packages/web/src/content/docs/nb/go.mdx | 28 ++++++++++---------- packages/web/src/content/docs/pl/go.mdx | 20 +++++++-------- packages/web/src/content/docs/pt-br/go.mdx | 28 ++++++++++---------- packages/web/src/content/docs/ru/go.mdx | 26 +++++++++---------- packages/web/src/content/docs/th/go.mdx | 30 +++++++++++----------- packages/web/src/content/docs/tr/go.mdx | 24 ++++++++--------- packages/web/src/content/docs/zh-cn/go.mdx | 28 ++++++++++---------- packages/web/src/content/docs/zh-tw/go.mdx | 28 ++++++++++---------- 17 files changed, 227 insertions(+), 227 deletions(-) diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 7539981d899c..a1551aa21e4d 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -100,27 +100,27 @@ OpenCode Go هو اشتراك منخفض التكلفة بقيمة **$10/شهر بالإضافة إلى OpenCode، تم التحقق من أن العملاء التاليين يعملون بصورة صحيحة مع OpenCode Go، مع أننا لا نضمن استمرارهم في العمل مستقبلًا. -| العميل | دعم الجلسات | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **Hermes** | ترسل البُنى التي تتضمن [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) الترويسة في طلبات OpenCode الرئيسية والمساعدة. دُمج الإصلاح بعد v0.21.0؛ ولا يتضمن ذلك الإصدار وحده الإصلاح. | -| **Claude Code** | يتعرف Go على ترويسة الجلسة الأصلية الخاصة به. ولا حاجة إلى غلاف لإضافة ترويسة مخصصة. | -| **Codex** | يتعرف Go على ترويسة الجلسة الأصلية الخاصة به. لا تزال بعض الإصدارات وإعدادات الوكيل تحذفها؛ لذا حافظ على ترويسة الجلسة عند إعادة توجيه الطلبات. | -| **ZCode** | يتعرف Go على ترويسة الجلسة الأصلية الخاصة به. لا يزال [طلبنا لإضافة `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) مفتوحًا، لكن إرسال هذه الترويسة بعينها لم يعد ضروريًا. | -| **Pi** | ترسل الإصدارات الحالية معلومات الجلسة إلى OpenCode. حدّث عمليات التثبيت الأقدم. | -| **jcode** | حدّث إلى **v0.81.6 أو إصدار أحدث**، إذ يتضمن [إصلاح ترويسة الجلسة](https://github.com/1jehuang/jcode/issues/1167). | -| **Kilo Code CLI** | تعيد الإصدارات التي تتضمن [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) ترويسات جلسات OpenCode. يشمل هذا الإصلاح CLI، وليس إضافة VS Code. راجع [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | +| العميل | دعم الجلسات | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | ترسل البُنى التي تتضمن [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) الترويسة في طلبات OpenCode الرئيسية والمساعدة. دُمج الإصلاح بعد v0.21.0؛ ولا يتضمن ذلك الإصدار وحده الإصلاح. | +| **Claude Code** | يتعرف Go على ترويسة الجلسة الأصلية الخاصة به. ولا حاجة إلى غلاف لإضافة ترويسة مخصصة. | +| **Codex** | يتعرف Go على ترويسة الجلسة الأصلية الخاصة به. لا تزال بعض الإصدارات وإعدادات الوكيل تحذفها؛ لذا حافظ على ترويسة الجلسة عند إعادة توجيه الطلبات. | +| **ZCode** | يتعرف Go على ترويسة الجلسة الأصلية الخاصة به. لا يزال [طلبنا لإضافة `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) مفتوحًا، لكن إرسال هذه الترويسة بعينها لم يعد ضروريًا. | +| **Pi** | ترسل الإصدارات الحالية معلومات الجلسة إلى OpenCode. حدّث عمليات التثبيت الأقدم. | +| **jcode** | حدّث إلى **v0.81.6 أو إصدار أحدث**، إذ يتضمن [إصلاح ترويسة الجلسة](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | تعيد الإصدارات التي تتضمن [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) ترويسات جلسات OpenCode. يشمل هذا الإصلاح CLI، وليس إضافة VS Code. راجع [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### العملاء المعروف وجود مشكلات لديهم دعم الجلسات مفقود أو غير مكتمل في إصدارات هؤلاء العملاء التي تحققنا منها. وتتابع البلاغات المرتبطة الإصلاحات والحلول البديلة. -| العميل | الحالة والمتابعة | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **DeepSeek Harness** | تصل معلومات الجلسة عبر بعض مسارات النماذج، لكنها تكون مفقودة عبر مسارات أخرى. نتعرف على ترويسة الجلسة الأصلية الخاصة به؛ والعمل المتبقي هو إرسالها عبر جميع المحولات. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | طُلب دعم ترويسة الجلسة تلقائيًا في [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | طُلب دعم ترويسة الجلسة تلقائيًا في [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | يتضمن [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) إصلاحًا مقترحًا في [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327)، ولم يُدمج بعد. | +| العميل | الحالة والمتابعة | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | تصل معلومات الجلسة عبر بعض مسارات النماذج، لكنها تكون مفقودة عبر مسارات أخرى. نتعرف على ترويسة الجلسة الأصلية الخاصة به؛ والعمل المتبقي هو إرسالها عبر جميع المحولات. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | طُلب دعم ترويسة الجلسة تلقائيًا في [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | طُلب دعم ترويسة الجلسة تلقائيًا في [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | يتضمن [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) إصلاحًا مقترحًا في [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327)، ولم يُدمج بعد. | ## حدود الاستخدام diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index c379aa291b21..e81627d99470 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -110,14 +110,14 @@ Vaš klijent treba: Pored OpenCode-a, potvrđeno je da sljedeći klijenti ispravno rade s OpenCode Go. Ipak, ne garantiramo da će nastaviti raditi i u budućnosti. -| Klijent | Podrška za sesije | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Hermes** | Buildovi koji sadrže [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) šalju zaglavlje u glavnim i pomoćnim OpenCode zahtjevima. Ispravka je spojena nakon verzije v0.21.0; samo to izdanje je ne sadrži. | -| **Claude Code** | Go prepoznaje njegovo izvorno zaglavlje sesije. Wrapper za prilagođeno zaglavlje nije potreban. | -| **Codex** | Go prepoznaje njegovo izvorno zaglavlje sesije. Neke verzije i proxy konfiguracije ga i dalje izostavljaju; sačuvajte zaglavlje sesije pri prosljeđivanju zahtjeva. | -| **ZCode** | Go prepoznaje njegovo izvorno zaglavlje sesije. Naš [zahtjev za `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) i dalje je otvoren, ali slanje tog konkretnog zaglavlja više nije potrebno. | -| **Pi** | Trenutni buildovi šalju informacije o sesiji za OpenCode. Ažurirajte starije instalacije. | -| **jcode** | Ažurirajte na **v0.81.6 ili noviju**, koja uključuje [ispravku zaglavlja sesije](https://github.com/1jehuang/jcode/issues/1167). | +| Klijent | Podrška za sesije | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Buildovi koji sadrže [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) šalju zaglavlje u glavnim i pomoćnim OpenCode zahtjevima. Ispravka je spojena nakon verzije v0.21.0; samo to izdanje je ne sadrži. | +| **Claude Code** | Go prepoznaje njegovo izvorno zaglavlje sesije. Wrapper za prilagođeno zaglavlje nije potreban. | +| **Codex** | Go prepoznaje njegovo izvorno zaglavlje sesije. Neke verzije i proxy konfiguracije ga i dalje izostavljaju; sačuvajte zaglavlje sesije pri prosljeđivanju zahtjeva. | +| **ZCode** | Go prepoznaje njegovo izvorno zaglavlje sesije. Naš [zahtjev za `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) i dalje je otvoren, ali slanje tog konkretnog zaglavlja više nije potrebno. | +| **Pi** | Trenutni buildovi šalju informacije o sesiji za OpenCode. Ažurirajte starije instalacije. | +| **jcode** | Ažurirajte na **v0.81.6 ili noviju**, koja uključuje [ispravku zaglavlja sesije](https://github.com/1jehuang/jcode/issues/1167). | | **Kilo Code CLI** | Buildovi koji sadrže [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) vraćaju OpenCode zaglavlja sesije. Ova ispravka obuhvata CLI, ali ne i VS Code ekstenziju. Pogledajte [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Poznati problematični klijenti @@ -125,12 +125,12 @@ s OpenCode Go. Ipak, ne garantiramo da će nastaviti raditi i u budućnosti. Ovim klijentima nedostaje podrška za sesije ili je ona nepotpuna u verzijama koje smo istražili. Povezani izvještaji prate ispravke i zaobilazna rješenja. -| Klijent | Status i praćenje | -| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **DeepSeek Harness** | Informacije o sesiji stižu za neke putanje modela, ali nedostaju za druge. Prepoznajemo njegovo izvorno zaglavlje; preostaje da se ono šalje kroz sve adaptere. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | Automatska podrška za zaglavlje sesije zatražena je u [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | Automatska podrška za zaglavlje sesije zatražena je u [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) ima predloženu ispravku u [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), koji još nije spojen. | +| Klijent | Status i praćenje | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | Informacije o sesiji stižu za neke putanje modela, ali nedostaju za druge. Prepoznajemo njegovo izvorno zaglavlje; preostaje da se ono šalje kroz sve adaptere. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | Automatska podrška za zaglavlje sesije zatražena je u [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Automatska podrška za zaglavlje sesije zatražena je u [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) ima predloženu ispravku u [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), koji još nije spojen. | ## Ograničenja upotrebe diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 0208563cb649..bfe8da867e95 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -111,13 +111,13 @@ Ud over OpenCode er følgende klienter blevet valideret til at fungere korrekt med OpenCode Go. Vi garanterer dog ikke, at de fortsat vil fungere fremover. | Klient | Sessionsunderstøttelse | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Hermes** | Builds, der indeholder [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864), sender headeren i primære og sekundære OpenCode-anmodninger. Rettelsen blev merged efter v0.21.0; den version indeholder den ikke i sig selv. | -| **Claude Code** | Go genkender dens indbyggede sessionsheader. En wrapper til en brugerdefineret header er ikke nødvendig. | -| **Codex** | Go genkender dens indbyggede sessionsheader. Nogle versioner og proxyopsætninger udelader den stadig; bevar sessionsheaderen, når anmodninger videresendes. | -| **ZCode** | Go genkender dens indbyggede sessionsheader. Vores [anmodning om `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) er stadig åben, men det er ikke længere nødvendigt at sende netop denne header. | -| **Pi** | Aktuelle builds sender sessionsoplysninger til OpenCode. Opdater ældre installationer. | -| **jcode** | Opdater til **v0.81.6 eller nyere**, som indeholder [rettelsen til sessionsheaderen](https://github.com/1jehuang/jcode/issues/1167). | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Builds, der indeholder [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864), sender headeren i primære og sekundære OpenCode-anmodninger. Rettelsen blev merged efter v0.21.0; den version indeholder den ikke i sig selv. | +| **Claude Code** | Go genkender dens indbyggede sessionsheader. En wrapper til en brugerdefineret header er ikke nødvendig. | +| **Codex** | Go genkender dens indbyggede sessionsheader. Nogle versioner og proxyopsætninger udelader den stadig; bevar sessionsheaderen, når anmodninger videresendes. | +| **ZCode** | Go genkender dens indbyggede sessionsheader. Vores [anmodning om `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) er stadig åben, men det er ikke længere nødvendigt at sende netop denne header. | +| **Pi** | Aktuelle builds sender sessionsoplysninger til OpenCode. Opdater ældre installationer. | +| **jcode** | Opdater til **v0.81.6 eller nyere**, som indeholder [rettelsen til sessionsheaderen](https://github.com/1jehuang/jcode/issues/1167). | | **Kilo Code CLI** | Builds, der indeholder [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752), gendanner OpenCode-sessionsheadere. Denne rettelse dækker CLI'en, men ikke VS Code-udvidelsen. Se [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Kendte problematiske klienter @@ -125,12 +125,12 @@ med OpenCode Go. Vi garanterer dog ikke, at de fortsat vil fungere fremover. Disse klienter mangler sessionsunderstøttelse eller har ufuldstændig understøttelse i de versioner, vi undersøgte. De linkede rapporter følger rettelser og løsninger. -| Klient | Status og opfølgning | -| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Klient | Status og opfølgning | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **DeepSeek Harness** | Sessionsoplysninger modtages på nogle modelstier, men mangler på andre. Vi genkender dens indbyggede header; det resterende arbejde er at sende den gennem alle adaptere. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | Automatisk understøttelse af sessionsheaderen er efterspurgt i [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | Automatisk understøttelse af sessionsheaderen er efterspurgt i [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) har en foreslået rettelse i [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), som endnu ikke er merged. | +| **GitHub Copilot Chat** | Automatisk understøttelse af sessionsheaderen er efterspurgt i [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Automatisk understøttelse af sessionsheaderen er efterspurgt i [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) har en foreslået rettelse i [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), som endnu ikke er merged. | ## Forbrugsgrænser diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index 624bf78338b7..dd37155b818d 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -102,27 +102,27 @@ Dein Client sollte: Neben OpenCode wurden die folgenden Clients für die ordnungsgemäße Verwendung mit OpenCode Go validiert. Wir garantieren jedoch nicht, dass sie auch in Zukunft funktionieren werden. -| Client | Sitzungsunterstützung | -| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Hermes** | Builds mit [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) senden den Header bei Haupt- und zusätzlichen OpenCode-Anfragen. Der Fix wurde nach v0.21.0 gemergt; diese Version allein enthält ihn nicht. | -| **Claude Code** | Go erkennt seinen nativen Sitzungs-Header. Es ist kein Wrapper für benutzerdefinierte Header erforderlich. | -| **Codex** | Go erkennt seinen nativen Sitzungs-Header. Einige Versionen und Proxy-Konfigurationen lassen ihn weiterhin weg; stelle sicher, dass der Sitzungs-Header beim Weiterleiten von Anfragen erhalten bleibt. | -| **ZCode** | Go erkennt seinen nativen Sitzungs-Header. Unsere [Anfrage für `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) ist weiterhin offen, aber es ist nicht mehr erforderlich, genau diesen Header zu senden. | -| **Pi** | Aktuelle Builds senden Sitzungsinformationen für OpenCode. Aktualisiere ältere Installationen. | -| **jcode** | Aktualisiere auf **v0.81.6 oder neuer**. Diese Version enthält den [Fix für den Sitzungs-Header](https://github.com/1jehuang/jcode/issues/1167). | -| **Kilo Code CLI** | Builds mit [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) stellen OpenCode-Sitzungs-Header wieder her. Dieser Fix gilt für die CLI, nicht für die VS-Code-Erweiterung. Siehe [Issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | +| Client | Sitzungsunterstützung | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Builds mit [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) senden den Header bei Haupt- und zusätzlichen OpenCode-Anfragen. Der Fix wurde nach v0.21.0 gemergt; diese Version allein enthält ihn nicht. | +| **Claude Code** | Go erkennt seinen nativen Sitzungs-Header. Es ist kein Wrapper für benutzerdefinierte Header erforderlich. | +| **Codex** | Go erkennt seinen nativen Sitzungs-Header. Einige Versionen und Proxy-Konfigurationen lassen ihn weiterhin weg; stelle sicher, dass der Sitzungs-Header beim Weiterleiten von Anfragen erhalten bleibt. | +| **ZCode** | Go erkennt seinen nativen Sitzungs-Header. Unsere [Anfrage für `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) ist weiterhin offen, aber es ist nicht mehr erforderlich, genau diesen Header zu senden. | +| **Pi** | Aktuelle Builds senden Sitzungsinformationen für OpenCode. Aktualisiere ältere Installationen. | +| **jcode** | Aktualisiere auf **v0.81.6 oder neuer**. Diese Version enthält den [Fix für den Sitzungs-Header](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Builds mit [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) stellen OpenCode-Sitzungs-Header wieder her. Dieser Fix gilt für die CLI, nicht für die VS-Code-Erweiterung. Siehe [Issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Bekannte problematische Clients Bei diesen Clients fehlt in den von uns untersuchten Versionen die Sitzungsunterstützung oder sie ist unvollständig. Die verlinkten Berichte dokumentieren Fixes und Problemumgehungen. -| Client | Status und Nachverfolgung | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Client | Status und Nachverfolgung | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **DeepSeek Harness** | Sitzungsinformationen werden bei einigen Modellpfaden übermittelt, fehlen aber bei anderen. Wir erkennen seinen nativen Header; dieser muss noch von allen Adaptern gesendet werden. [Diskussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | Automatische Unterstützung für Sitzungs-Header wurde in [VS Code Issue #334186](https://github.com/microsoft/vscode/issues/334186) angefragt. | -| **Kimi Code** | Automatische Unterstützung für Sitzungs-Header wurde in [Issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) angefragt. | -| **MiMo Code** | Für [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) gibt es einen vorgeschlagenen Fix in [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), der noch nicht gemergt wurde. | +| **GitHub Copilot Chat** | Automatische Unterstützung für Sitzungs-Header wurde in [VS Code Issue #334186](https://github.com/microsoft/vscode/issues/334186) angefragt. | +| **Kimi Code** | Automatische Unterstützung für Sitzungs-Header wurde in [Issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) angefragt. | +| **MiMo Code** | Für [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) gibt es einen vorgeschlagenen Fix in [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), der noch nicht gemergt wurde. | ## Nutzungslimits diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index 3ec6cab9cc68..de8405114971 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -110,14 +110,14 @@ Tu cliente debe: Además de OpenCode, se ha validado que los siguientes clientes funcionan correctamente con OpenCode Go. Sin embargo, no garantizamos que sigan funcionando en el futuro. -| Cliente | Compatibilidad con sesiones | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Hermes** | Las compilaciones que incluyen el [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) envían el encabezado en las peticiones principales y auxiliares de OpenCode. La corrección se fusionó después de v0.21.0; esa versión por sí sola no la incluye. | -| **Claude Code** | Go reconoce su encabezado de sesión nativo. No se necesita ningún wrapper para encabezados personalizados. | -| **Codex** | Go reconoce su encabezado de sesión nativo. Algunas versiones y configuraciones de proxy aún lo omiten; conserva el encabezado de sesión al reenviar peticiones. | -| **ZCode** | Go reconoce su encabezado de sesión nativo. Nuestra [solicitud de `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) sigue abierta, pero ya no es necesario enviar ese encabezado específico. | -| **Pi** | Las compilaciones actuales envían información de sesión para OpenCode. Actualiza las instalaciones antiguas. | -| **jcode** | Actualiza a la versión **v0.81.6 o posterior**, que incluye la [corrección del encabezado de sesión](https://github.com/1jehuang/jcode/issues/1167). | +| Cliente | Compatibilidad con sesiones | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Las compilaciones que incluyen el [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) envían el encabezado en las peticiones principales y auxiliares de OpenCode. La corrección se fusionó después de v0.21.0; esa versión por sí sola no la incluye. | +| **Claude Code** | Go reconoce su encabezado de sesión nativo. No se necesita ningún wrapper para encabezados personalizados. | +| **Codex** | Go reconoce su encabezado de sesión nativo. Algunas versiones y configuraciones de proxy aún lo omiten; conserva el encabezado de sesión al reenviar peticiones. | +| **ZCode** | Go reconoce su encabezado de sesión nativo. Nuestra [solicitud de `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) sigue abierta, pero ya no es necesario enviar ese encabezado específico. | +| **Pi** | Las compilaciones actuales envían información de sesión para OpenCode. Actualiza las instalaciones antiguas. | +| **jcode** | Actualiza a la versión **v0.81.6 o posterior**, que incluye la [corrección del encabezado de sesión](https://github.com/1jehuang/jcode/issues/1167). | | **Kilo Code CLI** | Las compilaciones que incluyen el [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) restauran los encabezados de sesión de OpenCode. Esta corrección cubre la CLI, no la extensión de VS Code. Consulta el [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Clientes con problemas conocidos @@ -125,12 +125,12 @@ con OpenCode Go. Sin embargo, no garantizamos que sigan funcionando en el futuro Estos clientes tienen una compatibilidad con sesiones ausente o incompleta en las versiones que investigamos. Los informes enlazados permiten seguir las correcciones y las soluciones alternativas. -| Cliente | Estado y seguimiento | -| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **DeepSeek Harness** | La información de sesión llega por algunas rutas de modelos, pero falta en otras. Reconocemos su encabezado nativo; aún es necesario enviarlo a través de todos los adaptadores. [Discusión #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | La compatibilidad automática con el encabezado de sesión se solicitó en el [issue #334186 de VS Code](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | La compatibilidad automática con el encabezado de sesión se solicitó en el [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | El [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) tiene una corrección propuesta en el [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), que aún no se ha fusionado. | +| Cliente | Estado y seguimiento | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | La información de sesión llega por algunas rutas de modelos, pero falta en otras. Reconocemos su encabezado nativo; aún es necesario enviarlo a través de todos los adaptadores. [Discusión #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | La compatibilidad automática con el encabezado de sesión se solicitó en el [issue #334186 de VS Code](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | La compatibilidad automática con el encabezado de sesión se solicitó en el [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | El [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) tiene una corrección propuesta en el [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), que aún no se ha fusionado. | ## Límites de uso diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 84390f198a70..97f3ecd00518 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -100,27 +100,27 @@ Votre client doit : Outre OpenCode, le bon fonctionnement des clients suivants avec OpenCode Go a été validé. Nous ne garantissons toutefois pas qu'ils continueront à fonctionner à l'avenir. -| Client | Prise en charge des sessions | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Hermes** | Les builds contenant la [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) envoient l'en-tête sur les requêtes OpenCode principales et auxiliaires. Le correctif a été fusionné après la v0.21.0 ; cette version seule ne l'inclut pas. | -| **Claude Code** | Go reconnaît son en-tête de session natif. Aucun wrapper d'en-tête personnalisé n'est nécessaire. | -| **Codex** | Go reconnaît son en-tête de session natif. Certaines versions et configurations de proxy l'omettent encore ; conservez l'en-tête de session lors du transfert des requêtes. | -| **ZCode** | Go reconnaît son en-tête de session natif. Notre [demande concernant `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) reste ouverte, mais il n'est plus nécessaire d'envoyer spécifiquement cet en-tête. | -| **Pi** | Les builds actuels envoient les informations de session pour OpenCode. Mettez à jour les installations plus anciennes. | -| **jcode** | Passez à la version **v0.81.6 ou ultérieure**, qui inclut le [correctif de l'en-tête de session](https://github.com/1jehuang/jcode/issues/1167). | -| **Kilo Code CLI** | Les builds contenant la [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) rétablissent les en-têtes de session OpenCode. Ce correctif concerne la CLI, pas l'extension VS Code. Consultez l'[issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | +| Client | Prise en charge des sessions | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Les builds contenant la [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) envoient l'en-tête sur les requêtes OpenCode principales et auxiliaires. Le correctif a été fusionné après la v0.21.0 ; cette version seule ne l'inclut pas. | +| **Claude Code** | Go reconnaît son en-tête de session natif. Aucun wrapper d'en-tête personnalisé n'est nécessaire. | +| **Codex** | Go reconnaît son en-tête de session natif. Certaines versions et configurations de proxy l'omettent encore ; conservez l'en-tête de session lors du transfert des requêtes. | +| **ZCode** | Go reconnaît son en-tête de session natif. Notre [demande concernant `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) reste ouverte, mais il n'est plus nécessaire d'envoyer spécifiquement cet en-tête. | +| **Pi** | Les builds actuels envoient les informations de session pour OpenCode. Mettez à jour les installations plus anciennes. | +| **jcode** | Passez à la version **v0.81.6 ou ultérieure**, qui inclut le [correctif de l'en-tête de session](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Les builds contenant la [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) rétablissent les en-têtes de session OpenCode. Ce correctif concerne la CLI, pas l'extension VS Code. Consultez l'[issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Clients connus pour poser problème Dans les versions que nous avons examinées, ces clients ne prennent pas en charge les sessions ou ne les prennent en charge que partiellement. Les rapports associés permettent de suivre les correctifs et les solutions de contournement. -| Client | État et suivi | -| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Client | État et suivi | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **DeepSeek Harness** | Les informations de session sont présentes pour certains chemins de modèles, mais absentes pour d'autres. Nous reconnaissons son en-tête natif ; il reste à l'envoyer depuis tous les adaptateurs. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | La prise en charge automatique de l'en-tête de session fait l'objet de l'[issue VS Code #334186](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | La prise en charge automatique de l'en-tête de session fait l'objet de l'[issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | L'[issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) dispose d'un correctif proposé dans la [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), qui n'a pas encore été fusionnée. | +| **GitHub Copilot Chat** | La prise en charge automatique de l'en-tête de session fait l'objet de l'[issue VS Code #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | La prise en charge automatique de l'en-tête de session fait l'objet de l'[issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | L'[issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) dispose d'un correctif proposé dans la [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), qui n'a pas encore été fusionnée. | ## Limites d'utilisation diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index eddc58f34837..88491dc1b06f 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -108,14 +108,14 @@ Il tuo client deve: Oltre a OpenCode, è stato verificato che i seguenti client funzionino correttamente con OpenCode Go. Tuttavia, non garantiamo che continueranno a funzionare in futuro. -| Client | Supporto delle sessioni | -| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Hermes** | Le build contenenti la [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) inviano l'header nelle richieste OpenCode principali e ausiliarie. La correzione è stata unita dopo la v0.21.0; quella versione da sola non la include. | -| **Claude Code** | Go riconosce il suo header di sessione nativo. Non è necessario alcun wrapper per header personalizzati. | -| **Codex** | Go riconosce il suo header di sessione nativo. Alcune versioni e configurazioni proxy continuano a ometterlo; mantieni l'header di sessione quando inoltri le richieste. | -| **ZCode** | Go riconosce il suo header di sessione nativo. La nostra [richiesta per `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) è ancora aperta, ma non è più necessario inviare proprio quell'header. | -| **Pi** | Le build attuali inviano le informazioni di sessione per OpenCode. Aggiorna le installazioni meno recenti. | -| **jcode** | Aggiorna alla versione **v0.81.6 o successiva**, che include la [correzione dell'header di sessione](https://github.com/1jehuang/jcode/issues/1167). | +| Client | Supporto delle sessioni | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Le build contenenti la [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) inviano l'header nelle richieste OpenCode principali e ausiliarie. La correzione è stata unita dopo la v0.21.0; quella versione da sola non la include. | +| **Claude Code** | Go riconosce il suo header di sessione nativo. Non è necessario alcun wrapper per header personalizzati. | +| **Codex** | Go riconosce il suo header di sessione nativo. Alcune versioni e configurazioni proxy continuano a ometterlo; mantieni l'header di sessione quando inoltri le richieste. | +| **ZCode** | Go riconosce il suo header di sessione nativo. La nostra [richiesta per `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) è ancora aperta, ma non è più necessario inviare proprio quell'header. | +| **Pi** | Le build attuali inviano le informazioni di sessione per OpenCode. Aggiorna le installazioni meno recenti. | +| **jcode** | Aggiorna alla versione **v0.81.6 o successiva**, che include la [correzione dell'header di sessione](https://github.com/1jehuang/jcode/issues/1167). | | **Kilo Code CLI** | Le build contenenti la [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) ripristinano gli header di sessione OpenCode. Questa correzione riguarda la CLI, non l'estensione VS Code. Consulta l'[issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Client con problemi noti @@ -123,12 +123,12 @@ con OpenCode Go. Tuttavia, non garantiamo che continueranno a funzionare in futu Nelle versioni che abbiamo esaminato, questi client non supportano le sessioni o le supportano solo parzialmente. Le segnalazioni collegate consentono di seguire le correzioni e le soluzioni alternative. -| Client | Stato e segnalazioni | -| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Client | Stato e segnalazioni | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **DeepSeek Harness** | Le informazioni di sessione arrivano tramite alcuni percorsi dei modelli, ma mancano in altri. Riconosciamo il suo header nativo; resta da inviarlo attraverso tutti gli adapter. [Discussione #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | Il supporto automatico dell'header di sessione è stato richiesto nell'[issue #334186 di VS Code](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | Il supporto automatico dell'header di sessione è stato richiesto nell'[issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | L'[issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) ha una correzione proposta nella [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), che non è stata ancora unita. | +| **GitHub Copilot Chat** | Il supporto automatico dell'header di sessione è stato richiesto nell'[issue #334186 di VS Code](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Il supporto automatico dell'header di sessione è stato richiesto nell'[issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | L'[issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) ha una correzione proposta nella [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), che non è stata ancora unita. | ## Limiti di utilizzo diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index d0f2207273ff..edaaf32d413a 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -97,14 +97,14 @@ OpenCode Goは、[OpenCode](https://opencode.ai)および同様の種類のリ OpenCodeに加えて、以下のクライアントがOpenCode Goで正常に動作することを確認しています。 ただし、今後も動作し続けることを保証するものではありません。 -| クライアント | セッション対応 | -| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Hermes** | [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864)を含むビルドでは、OpenCodeのメインリクエストと補助リクエストの両方でヘッダーが送信されます。この修正はv0.21.0より後にマージされたため、v0.21.0自体には含まれていません。 | -| **Claude Code** | GoはClaude Code固有のセッションヘッダーを認識します。カスタムヘッダーを追加するラッパーは不要です。 | -| **Codex** | GoはCodex固有のセッションヘッダーを認識します。一部のバージョンやプロキシ設定では引き続きヘッダーが省略されるため、リクエストを転送するときはセッションヘッダーを保持してください。 | -| **ZCode** | GoはZCode固有のセッションヘッダーを認識します。[`x-opencode-session`の追加リクエスト](https://github.com/zai-org/feedback/issues/492)は未解決ですが、この特定のヘッダーを送信する必要はなくなりました。 | -| **Pi** | 現在のビルドはOpenCodeにセッション情報を送信します。古いインストールは更新してください。 | -| **jcode** | [セッションヘッダーの修正](https://github.com/1jehuang/jcode/issues/1167)を含む**v0.81.6以降**に更新してください。 | +| クライアント | セッション対応 | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864)を含むビルドでは、OpenCodeのメインリクエストと補助リクエストの両方でヘッダーが送信されます。この修正はv0.21.0より後にマージされたため、v0.21.0自体には含まれていません。 | +| **Claude Code** | GoはClaude Code固有のセッションヘッダーを認識します。カスタムヘッダーを追加するラッパーは不要です。 | +| **Codex** | GoはCodex固有のセッションヘッダーを認識します。一部のバージョンやプロキシ設定では引き続きヘッダーが省略されるため、リクエストを転送するときはセッションヘッダーを保持してください。 | +| **ZCode** | GoはZCode固有のセッションヘッダーを認識します。[`x-opencode-session`の追加リクエスト](https://github.com/zai-org/feedback/issues/492)は未解決ですが、この特定のヘッダーを送信する必要はなくなりました。 | +| **Pi** | 現在のビルドはOpenCodeにセッション情報を送信します。古いインストールは更新してください。 | +| **jcode** | [セッションヘッダーの修正](https://github.com/1jehuang/jcode/issues/1167)を含む**v0.81.6以降**に更新してください。 | | **Kilo Code CLI** | [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752)を含むビルドでは、OpenCodeのセッションヘッダーが再び送信されます。この修正の対象はCLIであり、VS Code拡張機能ではありません。[issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723)を参照してください。 | ### 既知の問題があるクライアント @@ -112,12 +112,12 @@ OpenCodeに加えて、以下のクライアントがOpenCode Goで正常に動 調査したバージョンでは、以下のクライアントのセッション対応が欠けているか不完全です。 リンク先の報告で修正や回避策を追跡しています。 -| クライアント | 状況と追跡 | -| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| クライアント | 状況と追跡 | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **DeepSeek Harness** | 一部のモデルパスではセッション情報が届きますが、他のパスでは欠落します。GoはDeepSeek Harness固有のヘッダーを認識します。残る対応は、すべてのアダプターからそのヘッダーを送信することです。[Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495)。 | -| **GitHub Copilot Chat** | セッションヘッダーの自動送信対応は[VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186)で要望されています。 | -| **Kimi Code** | セッションヘッダーの自動送信対応は[issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506)で要望されています。 | -| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317)には[PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327)で修正案がありますが、まだマージされていません。 | +| **GitHub Copilot Chat** | セッションヘッダーの自動送信対応は[VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186)で要望されています。 | +| **Kimi Code** | セッションヘッダーの自動送信対応は[issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506)で要望されています。 | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317)には[PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327)で修正案がありますが、まだマージされていません。 | ## 利用制限 diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index e3989dca50fb..76770bdfd488 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -97,14 +97,14 @@ OpenCode Go는 [OpenCode](https://opencode.ai) 및 유사한 유형의 요청을 OpenCode 외에도 다음 클라이언트가 OpenCode Go에서 올바르게 작동하는 것으로 검증되었습니다. 다만 앞으로도 계속 작동할 것이라고 보장하지는 않습니다. -| 클라이언트 | 세션 지원 | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Hermes** | [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864)가 포함된 빌드는 OpenCode의 기본 요청과 보조 요청 모두에서 헤더를 전송합니다. 이 수정은 v0.21.0 이후에 병합되었으므로 v0.21.0 릴리스 자체에는 포함되지 않습니다. | -| **Claude Code** | Go는 Claude Code의 고유 세션 헤더를 인식합니다. 사용자 지정 헤더 래퍼가 필요하지 않습니다. | -| **Codex** | Go는 Codex의 고유 세션 헤더를 인식합니다. 일부 버전과 프록시 설정에서는 여전히 이 헤더가 누락되므로 요청을 전달할 때 세션 헤더를 유지하세요. | +| 클라이언트 | 세션 지원 | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Hermes** | [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864)가 포함된 빌드는 OpenCode의 기본 요청과 보조 요청 모두에서 헤더를 전송합니다. 이 수정은 v0.21.0 이후에 병합되었으므로 v0.21.0 릴리스 자체에는 포함되지 않습니다. | +| **Claude Code** | Go는 Claude Code의 고유 세션 헤더를 인식합니다. 사용자 지정 헤더 래퍼가 필요하지 않습니다. | +| **Codex** | Go는 Codex의 고유 세션 헤더를 인식합니다. 일부 버전과 프록시 설정에서는 여전히 이 헤더가 누락되므로 요청을 전달할 때 세션 헤더를 유지하세요. | | **ZCode** | Go는 ZCode의 고유 세션 헤더를 인식합니다. [`x-opencode-session` 요청](https://github.com/zai-org/feedback/issues/492)은 아직 열려 있지만 이제 해당 헤더를 별도로 전송할 필요는 없습니다. | -| **Pi** | 현재 빌드는 OpenCode에 세션 정보를 전송합니다. 이전 설치 버전은 업데이트하세요. | -| **jcode** | [세션 헤더 수정](https://github.com/1jehuang/jcode/issues/1167)이 포함된 **v0.81.6 이상**으로 업데이트하세요. | +| **Pi** | 현재 빌드는 OpenCode에 세션 정보를 전송합니다. 이전 설치 버전은 업데이트하세요. | +| **jcode** | [세션 헤더 수정](https://github.com/1jehuang/jcode/issues/1167)이 포함된 **v0.81.6 이상**으로 업데이트하세요. | | **Kilo Code CLI** | [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752)가 포함된 빌드는 OpenCode 세션 헤더를 복원합니다. 이 수정은 CLI에만 적용되며 VS Code 확장에는 적용되지 않습니다. [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723)을 참조하세요. | ### 알려진 문제가 있는 클라이언트 @@ -112,12 +112,12 @@ OpenCode 외에도 다음 클라이언트가 OpenCode Go에서 올바르게 작 조사한 버전에서 다음 클라이언트는 세션 지원이 없거나 불완전했습니다. 링크된 보고서에서 수정 사항과 해결 방법을 추적합니다. -| 클라이언트 | 상태 및 추적 | -| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 클라이언트 | 상태 및 추적 | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **DeepSeek Harness** | 일부 모델 경로에서는 세션 정보가 전달되지만 다른 경로에서는 누락됩니다. Go는 DeepSeek Harness의 고유 헤더를 인식하며, 남은 작업은 모든 어댑터에서 이 헤더를 전송하는 것입니다. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | 자동 세션 헤더 지원은 [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186)에서 요청되었습니다. | -| **Kimi Code** | 자동 세션 헤더 지원은 [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506)에서 요청되었습니다. | -| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317)에 [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327)로 수정안이 제안되었지만 아직 병합되지 않았습니다. | +| **GitHub Copilot Chat** | 자동 세션 헤더 지원은 [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186)에서 요청되었습니다. | +| **Kimi Code** | 자동 세션 헤더 지원은 [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506)에서 요청되었습니다. | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317)에 [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327)로 수정안이 제안되었지만 아직 병합되지 않았습니다. | ## 사용 한도 diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index fa8299900bef..29d41be06170 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -110,27 +110,27 @@ Klienten din skal: I tillegg til OpenCode er følgende klienter validert for å fungere riktig med OpenCode Go. Vi garanterer imidlertid ikke at de vil fortsette å fungere i fremtiden. -| Klient | Sesjonsstøtte | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Hermes** | Bygg som inneholder [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864), sender headeren i primære og sekundære OpenCode-forespørsler. Rettelsen ble slått sammen etter v0.21.0; den versjonen inneholder den ikke alene. | -| **Claude Code** | Go gjenkjenner den innebygde sesjonsheaderen. En wrapper for en egendefinert header er ikke nødvendig. | -| **Codex** | Go gjenkjenner den innebygde sesjonsheaderen. Noen versjoner og proxyoppsett utelater den fortsatt; behold sesjonsheaderen når forespørsler videresendes. | -| **ZCode** | Go gjenkjenner den innebygde sesjonsheaderen. Vår [forespørsel om `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) er fortsatt åpen, men det er ikke lenger nødvendig å sende akkurat denne headeren. | -| **Pi** | Gjeldende bygg sender sesjonsinformasjon for OpenCode. Oppdater eldre installasjoner. | -| **jcode** | Oppdater til **v0.81.6 eller nyere**, som inneholder [rettelsen for sesjonsheaderen](https://github.com/1jehuang/jcode/issues/1167). | -| **Kilo Code CLI** | Bygg som inneholder [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752), gjenoppretter OpenCode-sesjonsheadere. Denne rettelsen dekker CLI-en, ikke VS Code-utvidelsen. Se [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | +| Klient | Sesjonsstøtte | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Hermes** | Bygg som inneholder [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864), sender headeren i primære og sekundære OpenCode-forespørsler. Rettelsen ble slått sammen etter v0.21.0; den versjonen inneholder den ikke alene. | +| **Claude Code** | Go gjenkjenner den innebygde sesjonsheaderen. En wrapper for en egendefinert header er ikke nødvendig. | +| **Codex** | Go gjenkjenner den innebygde sesjonsheaderen. Noen versjoner og proxyoppsett utelater den fortsatt; behold sesjonsheaderen når forespørsler videresendes. | +| **ZCode** | Go gjenkjenner den innebygde sesjonsheaderen. Vår [forespørsel om `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) er fortsatt åpen, men det er ikke lenger nødvendig å sende akkurat denne headeren. | +| **Pi** | Gjeldende bygg sender sesjonsinformasjon for OpenCode. Oppdater eldre installasjoner. | +| **jcode** | Oppdater til **v0.81.6 eller nyere**, som inneholder [rettelsen for sesjonsheaderen](https://github.com/1jehuang/jcode/issues/1167). | +| **Kilo Code CLI** | Bygg som inneholder [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752), gjenoppretter OpenCode-sesjonsheadere. Denne rettelsen dekker CLI-en, ikke VS Code-utvidelsen. Se [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Kjente problematiske klienter Disse klientene mangler sesjonsstøtte eller har ufullstendig støtte i versjonene vi undersøkte. De lenkede rapportene følger rettelser og midlertidige løsninger. -| Klient | Status og oppfølging | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Klient | Status og oppfølging | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **DeepSeek Harness** | Sesjonsinformasjon mottas på noen modellbaner, men mangler på andre. Vi gjenkjenner den innebygde headeren; det gjenstående arbeidet er å sende den gjennom alle adaptere. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | Automatisk støtte for sesjonsheaderen er etterspurt i [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | Automatisk støtte for sesjonsheaderen er etterspurt i [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) har en foreslått rettelse i [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), som ennå ikke er slått sammen. | +| **GitHub Copilot Chat** | Automatisk støtte for sesjonsheaderen er etterspurt i [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Automatisk støtte for sesjonsheaderen er etterspurt i [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) har en foreslått rettelse i [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), som ennå ikke er slått sammen. | ## Bruksgrenser diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index 34737509154e..bc89ca4eed96 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -104,13 +104,13 @@ Twój klient powinien: Poza OpenCode zweryfikowano, że poniżsi klienci działają prawidłowo z OpenCode Go. Nie gwarantujemy jednak, że będą nadal działać w przyszłości. -| Klient | Obsługa sesji | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Klient | Obsługa sesji | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Hermes** | Kompilacje zawierające [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) wysyłają nagłówek w głównych i pomocniczych żądaniach OpenCode. Poprawkę scalono po wersji v0.21.0; samo to wydanie jej nie zawiera. | -| **Claude Code** | Go rozpoznaje jego natywny nagłówek sesji. Wrapper dodający niestandardowy nagłówek nie jest potrzebny. | +| **Claude Code** | Go rozpoznaje jego natywny nagłówek sesji. Wrapper dodający niestandardowy nagłówek nie jest potrzebny. | | **Codex** | Go rozpoznaje jego natywny nagłówek sesji. Niektóre wersje i konfiguracje proxy nadal go pomijają; podczas przekazywania żądań należy zachować nagłówek sesji. | -| **ZCode** | Go rozpoznaje jego natywny nagłówek sesji. Nasza [prośba o `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) pozostaje otwarta, ale wysyłanie tego konkretnego nagłówka nie jest już konieczne. | -| **Pi** | Aktualne kompilacje wysyłają informacje o sesji dla OpenCode. Zaktualizuj starsze instalacje. | +| **ZCode** | Go rozpoznaje jego natywny nagłówek sesji. Nasza [prośba o `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) pozostaje otwarta, ale wysyłanie tego konkretnego nagłówka nie jest już konieczne. | +| **Pi** | Aktualne kompilacje wysyłają informacje o sesji dla OpenCode. Zaktualizuj starsze instalacje. | | **jcode** | Zaktualizuj do wersji **v0.81.6 lub nowszej**, która zawiera [poprawkę nagłówka sesji](https://github.com/1jehuang/jcode/issues/1167). | | **Kilo Code CLI** | Kompilacje zawierające [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) przywracają nagłówki sesji OpenCode. Ta poprawka obejmuje CLI, ale nie rozszerzenie VS Code. Zobacz [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | @@ -119,12 +119,12 @@ z OpenCode Go. Nie gwarantujemy jednak, że będą nadal działać w przyszłoś W badanych przez nas wersjach tych klientów obsługa sesji jest niepełna lub jej brakuje. Raporty pod podanymi linkami służą do śledzenia poprawek i obejść. -| Klient | Stan i śledzenie | -| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Klient | Stan i śledzenie | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **DeepSeek Harness** | Informacje o sesji docierają w niektórych ścieżkach modeli, ale brakuje ich w innych. Rozpoznajemy jego natywny nagłówek; pozostaje zapewnić jego wysyłanie przez wszystkie adaptery. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | Prośbę o automatyczną obsługę nagłówka sesji zgłoszono w [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | Prośbę o automatyczną obsługę nagłówka sesji zgłoszono w [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | Dla [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) zaproponowano poprawkę w [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), który nie został jeszcze scalony. | +| **GitHub Copilot Chat** | Prośbę o automatyczną obsługę nagłówka sesji zgłoszono w [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Prośbę o automatyczną obsługę nagłówka sesji zgłoszono w [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | Dla [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) zaproponowano poprawkę w [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), który nie został jeszcze scalony. | ## Limity użycia diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index 0083a3a0677c..140166856e3c 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -110,14 +110,14 @@ Seu cliente deve: Além do OpenCode, os clientes a seguir foram validados para funcionar corretamente com o OpenCode Go. No entanto, não garantimos que continuarão funcionando no futuro. -| Cliente | Suporte a sessões | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **Hermes** | As builds que contêm o [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) enviam o header nas requisições principais e auxiliares do OpenCode. A correção foi incorporada após a v0.21.0; essa versão por si só não a inclui. | -| **Claude Code** | O Go reconhece seu header de sessão nativo. Não é necessário usar um wrapper para headers personalizados. | -| **Codex** | O Go reconhece seu header de sessão nativo. Algumas versões e configurações de proxy ainda o omitem; preserve o header de sessão ao encaminhar requisições. | -| **ZCode** | O Go reconhece seu header de sessão nativo. Nossa [solicitação de `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) continua aberta, mas não é mais necessário enviar especificamente esse header. | -| **Pi** | As builds atuais enviam informações de sessão para o OpenCode. Atualize instalações mais antigas. | -| **jcode** | Atualize para a versão **v0.81.6 ou posterior**, que inclui a [correção do header de sessão](https://github.com/1jehuang/jcode/issues/1167). | +| Cliente | Suporte a sessões | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | As builds que contêm o [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) enviam o header nas requisições principais e auxiliares do OpenCode. A correção foi incorporada após a v0.21.0; essa versão por si só não a inclui. | +| **Claude Code** | O Go reconhece seu header de sessão nativo. Não é necessário usar um wrapper para headers personalizados. | +| **Codex** | O Go reconhece seu header de sessão nativo. Algumas versões e configurações de proxy ainda o omitem; preserve o header de sessão ao encaminhar requisições. | +| **ZCode** | O Go reconhece seu header de sessão nativo. Nossa [solicitação de `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) continua aberta, mas não é mais necessário enviar especificamente esse header. | +| **Pi** | As builds atuais enviam informações de sessão para o OpenCode. Atualize instalações mais antigas. | +| **jcode** | Atualize para a versão **v0.81.6 ou posterior**, que inclui a [correção do header de sessão](https://github.com/1jehuang/jcode/issues/1167). | | **Kilo Code CLI** | As builds que contêm o [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) restauram os headers de sessão do OpenCode. Essa correção abrange a CLI, não a extensão do VS Code. Consulte a [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Clientes com problemas conhecidos @@ -125,12 +125,12 @@ com o OpenCode Go. No entanto, não garantimos que continuarão funcionando no f Esses clientes não têm suporte a sessões ou apresentam suporte incompleto nas versões que investigamos. Os relatórios vinculados acompanham correções e soluções alternativas. -| Cliente | Status e acompanhamento | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **DeepSeek Harness** | As informações de sessão chegam por alguns caminhos de modelos, mas não por outros. Reconhecemos seu header nativo; ainda é necessário enviá-lo por todos os adaptadores. [Discussão #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | O suporte automático ao header de sessão foi solicitado na [issue #334186 do VS Code](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | O suporte automático ao header de sessão foi solicitado na [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | A [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) tem uma correção proposta no [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), que ainda não foi incorporada. | +| Cliente | Status e acompanhamento | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | As informações de sessão chegam por alguns caminhos de modelos, mas não por outros. Reconhecemos seu header nativo; ainda é necessário enviá-lo por todos os adaptadores. [Discussão #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | +| **GitHub Copilot Chat** | O suporte automático ao header de sessão foi solicitado na [issue #334186 do VS Code](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | O suporte automático ao header de sessão foi solicitado na [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | A [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) tem uma correção proposta no [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), que ainda não foi incorporada. | ## Limites de uso diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index f56cfbb125ce..22337f326a4e 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -110,14 +110,14 @@ OpenCode Go предназначен для [OpenCode](https://opencode.ai) и Помимо OpenCode, корректная работа с OpenCode Go подтверждена для следующих клиентов. Однако мы не гарантируем, что они продолжат работать в будущем. -| Клиент | Поддержка сессий | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Hermes** | Сборки, содержащие [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864), отправляют заголовок в основных и вспомогательных запросах OpenCode. Исправление было объединено после v0.21.0; само это издание его не содержит. | -| **Claude Code** | Go распознает его нативный заголовок сессии. Обертка для добавления пользовательского заголовка не требуется. | -| **Codex** | Go распознает его нативный заголовок сессии. Некоторые версии и конфигурации прокси по-прежнему его не передают; сохраняйте заголовок сессии при пересылке запросов. | -| **ZCode** | Go распознает его нативный заголовок сессии. Наш [запрос на `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) остается открытым, но отправлять именно этот заголовок больше не требуется. | -| **Pi** | Текущие сборки отправляют информацию о сессии для OpenCode. Обновите более старые установки. | -| **jcode** | Обновитесь до версии **v0.81.6 или новее**, которая содержит [исправление заголовка сессии](https://github.com/1jehuang/jcode/issues/1167). | +| Клиент | Поддержка сессий | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | Сборки, содержащие [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864), отправляют заголовок в основных и вспомогательных запросах OpenCode. Исправление было объединено после v0.21.0; само это издание его не содержит. | +| **Claude Code** | Go распознает его нативный заголовок сессии. Обертка для добавления пользовательского заголовка не требуется. | +| **Codex** | Go распознает его нативный заголовок сессии. Некоторые версии и конфигурации прокси по-прежнему его не передают; сохраняйте заголовок сессии при пересылке запросов. | +| **ZCode** | Go распознает его нативный заголовок сессии. Наш [запрос на `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) остается открытым, но отправлять именно этот заголовок больше не требуется. | +| **Pi** | Текущие сборки отправляют информацию о сессии для OpenCode. Обновите более старые установки. | +| **jcode** | Обновитесь до версии **v0.81.6 или новее**, которая содержит [исправление заголовка сессии](https://github.com/1jehuang/jcode/issues/1167). | | **Kilo Code CLI** | Сборки, содержащие [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752), восстанавливают заголовки сессии OpenCode. Это исправление относится к CLI, но не к расширению VS Code. См. [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Клиенты с известными проблемами @@ -125,12 +125,12 @@ OpenCode Go предназначен для [OpenCode](https://opencode.ai) и В исследованных нами версиях этих клиентов поддержка сессий отсутствует или реализована не полностью. По ссылкам можно отслеживать исправления и обходные решения. -| Клиент | Статус и отслеживание | -| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Клиент | Статус и отслеживание | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **DeepSeek Harness** | Информация о сессии поступает для некоторых путей моделей, но отсутствует для других. Мы распознаем его нативный заголовок; остается обеспечить его отправку через все адаптеры. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | Запрос на автоматическую поддержку заголовка сессии создан в [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | -| **Kimi Code** | Запрос на автоматическую поддержку заголовка сессии создан в [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | -| **MiMo Code** | Для [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) предложено исправление в [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), который еще не объединен. | +| **GitHub Copilot Chat** | Запрос на автоматическую поддержку заголовка сессии создан в [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186). | +| **Kimi Code** | Запрос на автоматическую поддержку заголовка сессии создан в [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506). | +| **MiMo Code** | Для [issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) предложено исправление в [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327), который еще не объединен. | ## Лимиты использования diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index 621599d46f3a..29f5448a4d80 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -97,27 +97,27 @@ OpenCode Go ออกแบบมาสำหรับ [OpenCode](https://openco นอกจาก OpenCode แล้ว ไคลเอนต์ต่อไปนี้ได้รับการตรวจสอบแล้วว่าทำงานกับ OpenCode Go ได้อย่างถูกต้อง อย่างไรก็ตาม เราไม่รับประกันว่าไคลเอนต์เหล่านี้จะยังคงทำงานได้ต่อไปในอนาคต -| ไคลเอนต์ | การรองรับเซสชัน | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **Hermes** | บิลด์ที่มี [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) จะส่งส่วนหัวในคำขอ OpenCode ทั้งคำขอหลักและคำขอเสริม การแก้ไขนี้ถูก merge หลัง v0.21.0 ดังนั้นตัวรีลีส v0.21.0 เองจึงยังไม่มีการแก้ไขนี้ | -| **Claude Code** | Go รู้จักส่วนหัวเซสชันแบบ native ของ Claude Code จึงไม่ต้องใช้ wrapper เพื่อเพิ่มส่วนหัวแบบกำหนดเอง | -| **Codex** | Go รู้จักส่วนหัวเซสชันแบบ native ของ Codex แต่บางเวอร์ชันและการตั้งค่า proxy ยังไม่ส่งส่วนหัวนี้ โปรดคงส่วนหัวเซสชันไว้เมื่อ forward คำขอ | -| **ZCode** | Go รู้จักส่วนหัวเซสชันแบบ native ของ ZCode [คำขอให้เพิ่ม `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) ของเรายังเปิดอยู่ แต่ไม่จำเป็นต้องส่งส่วนหัวเฉพาะนี้อีกต่อไป | -| **Pi** | บิลด์ปัจจุบันส่งข้อมูลเซสชันสำหรับ OpenCode โปรดอัปเดตการติดตั้งเวอร์ชันเก่า | -| **jcode** | อัปเดตเป็น **v0.81.6 หรือใหม่กว่า** ซึ่งมี[การแก้ไขส่วนหัวเซสชัน](https://github.com/1jehuang/jcode/issues/1167) | -| **Kilo Code CLI** | บิลด์ที่มี [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) จะทำให้ส่วนหัวเซสชันของ OpenCode กลับมาทำงาน การแก้ไขนี้ครอบคลุม CLI แต่ไม่ครอบคลุมส่วนขยาย VS Code ดู [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723) | +| ไคลเอนต์ | การรองรับเซสชัน | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Hermes** | บิลด์ที่มี [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) จะส่งส่วนหัวในคำขอ OpenCode ทั้งคำขอหลักและคำขอเสริม การแก้ไขนี้ถูก merge หลัง v0.21.0 ดังนั้นตัวรีลีส v0.21.0 เองจึงยังไม่มีการแก้ไขนี้ | +| **Claude Code** | Go รู้จักส่วนหัวเซสชันแบบ native ของ Claude Code จึงไม่ต้องใช้ wrapper เพื่อเพิ่มส่วนหัวแบบกำหนดเอง | +| **Codex** | Go รู้จักส่วนหัวเซสชันแบบ native ของ Codex แต่บางเวอร์ชันและการตั้งค่า proxy ยังไม่ส่งส่วนหัวนี้ โปรดคงส่วนหัวเซสชันไว้เมื่อ forward คำขอ | +| **ZCode** | Go รู้จักส่วนหัวเซสชันแบบ native ของ ZCode [คำขอให้เพิ่ม `x-opencode-session`](https://github.com/zai-org/feedback/issues/492) ของเรายังเปิดอยู่ แต่ไม่จำเป็นต้องส่งส่วนหัวเฉพาะนี้อีกต่อไป | +| **Pi** | บิลด์ปัจจุบันส่งข้อมูลเซสชันสำหรับ OpenCode โปรดอัปเดตการติดตั้งเวอร์ชันเก่า | +| **jcode** | อัปเดตเป็น **v0.81.6 หรือใหม่กว่า** ซึ่งมี[การแก้ไขส่วนหัวเซสชัน](https://github.com/1jehuang/jcode/issues/1167) | +| **Kilo Code CLI** | บิลด์ที่มี [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) จะทำให้ส่วนหัวเซสชันของ OpenCode กลับมาทำงาน การแก้ไขนี้ครอบคลุม CLI แต่ไม่ครอบคลุมส่วนขยาย VS Code ดู [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723) | ### ไคลเอนต์ที่ทราบว่ามีปัญหา ไคลเอนต์เหล่านี้ไม่รองรับเซสชันหรือรองรับไม่สมบูรณ์ในเวอร์ชันที่เราตรวจสอบ รายงานที่ลิงก์ไว้ใช้ติดตามการแก้ไขและวิธีแก้ปัญหาชั่วคราว -| ไคลเอนต์ | สถานะและการติดตาม | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **DeepSeek Harness** | ข้อมูลเซสชันส่งมาถึงใน model path บางรายการ แต่ขาดหายไปในรายการอื่น Go รู้จักส่วนหัวแบบ native ของ DeepSeek Harness งานที่เหลือคือการส่งส่วนหัวนี้ผ่าน adapter ทั้งหมด [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495) | -| **GitHub Copilot Chat** | มีการขอให้รองรับส่วนหัวเซสชันโดยอัตโนมัติใน [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) | -| **Kimi Code** | มีการขอให้รองรับส่วนหัวเซสชันโดยอัตโนมัติใน [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) | -| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) มีการเสนอการแก้ไขใน [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327) ซึ่งยังไม่ได้ merge | +| ไคลเอนต์ | สถานะและการติดตาม | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | ข้อมูลเซสชันส่งมาถึงใน model path บางรายการ แต่ขาดหายไปในรายการอื่น Go รู้จักส่วนหัวแบบ native ของ DeepSeek Harness งานที่เหลือคือการส่งส่วนหัวนี้ผ่าน adapter ทั้งหมด [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495) | +| **GitHub Copilot Chat** | มีการขอให้รองรับส่วนหัวเซสชันโดยอัตโนมัติใน [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) | +| **Kimi Code** | มีการขอให้รองรับส่วนหัวเซสชันโดยอัตโนมัติใน [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) มีการเสนอการแก้ไขใน [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327) ซึ่งยังไม่ได้ merge | ## Usage limits diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index 0cd0cd0220f0..764de377cfd3 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -97,27 +97,27 @@ Trafik, diğer kullanıcıların deneyimini olumsuz etkileyen kötüye kullanım OpenCode'un yanı sıra aşağıdaki istemcilerin OpenCode Go ile düzgün çalıştığı doğrulanmıştır. Ancak gelecekte de çalışmaya devam edeceklerini garanti etmiyoruz. -| İstemci | Oturum desteği | -| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Hermes** | [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) içeren derlemeler, ana ve yardımcı OpenCode isteklerinde başlığı gönderir. Düzeltme v0.21.0'dan sonra birleştirilmiştir; dolayısıyla v0.21.0 sürümü tek başına bu düzeltmeyi içermez. | -| **Claude Code** | Go, Claude Code'un yerel oturum başlığını tanır. Özel başlık ekleyen bir sarmalayıcı gerekmez. | -| **Codex** | Go, Codex'in yerel oturum başlığını tanır. Bazı sürümler ve proxy yapılandırmaları hâlâ bu başlığı göndermemektedir; istekleri iletirken oturum başlığını koruyun. | -| **ZCode** | Go, ZCode'un yerel oturum başlığını tanır. [`x-opencode-session` talebimiz](https://github.com/zai-org/feedback/issues/492) hâlâ açıktır ancak artık özellikle bu başlığın gönderilmesi gerekli değildir. | -| **Pi** | Güncel derlemeler OpenCode için oturum bilgilerini gönderir. Eski kurulumları güncelleyin. | -| **jcode** | [Oturum başlığı düzeltmesini](https://github.com/1jehuang/jcode/issues/1167) içeren **v0.81.6 veya sonraki bir sürüme** güncelleyin. | -| **Kilo Code CLI** | [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) içeren derlemeler OpenCode oturum başlıklarını geri getirir. Bu düzeltme CLI'ı kapsar, VS Code uzantısını kapsamaz. Bkz. [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | +| İstemci | Oturum desteği | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Hermes** | [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) içeren derlemeler, ana ve yardımcı OpenCode isteklerinde başlığı gönderir. Düzeltme v0.21.0'dan sonra birleştirilmiştir; dolayısıyla v0.21.0 sürümü tek başına bu düzeltmeyi içermez. | +| **Claude Code** | Go, Claude Code'un yerel oturum başlığını tanır. Özel başlık ekleyen bir sarmalayıcı gerekmez. | +| **Codex** | Go, Codex'in yerel oturum başlığını tanır. Bazı sürümler ve proxy yapılandırmaları hâlâ bu başlığı göndermemektedir; istekleri iletirken oturum başlığını koruyun. | +| **ZCode** | Go, ZCode'un yerel oturum başlığını tanır. [`x-opencode-session` talebimiz](https://github.com/zai-org/feedback/issues/492) hâlâ açıktır ancak artık özellikle bu başlığın gönderilmesi gerekli değildir. | +| **Pi** | Güncel derlemeler OpenCode için oturum bilgilerini gönderir. Eski kurulumları güncelleyin. | +| **jcode** | [Oturum başlığı düzeltmesini](https://github.com/1jehuang/jcode/issues/1167) içeren **v0.81.6 veya sonraki bir sürüme** güncelleyin. | +| **Kilo Code CLI** | [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) içeren derlemeler OpenCode oturum başlıklarını geri getirir. Bu düzeltme CLI'ı kapsar, VS Code uzantısını kapsamaz. Bkz. [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723). | ### Sorunlu Olduğu Bilinen İstemciler Bu istemcilerin incelediğimiz sürümlerinde oturum desteği eksik veya tamamlanmamıştır. Bağlantı verilen bildirimlerden düzeltmeleri ve geçici çözümleri takip edebilirsiniz. -| İstemci | Durum ve takip | +| İstemci | Durum ve takip | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **DeepSeek Harness** | Oturum bilgileri bazı model yollarında ulaşırken diğerlerinde eksiktir. Go, DeepSeek Harness'ın yerel başlığını tanır; yapılması gereken, bu başlığın tüm adaptörlerden gönderilmesidir. [Discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495). | -| **GitHub Copilot Chat** | Otomatik oturum başlığı desteği [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) içinde talep edilmiştir. | +| **GitHub Copilot Chat** | Otomatik oturum başlığı desteği [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) içinde talep edilmiştir. | | **Kimi Code** | Otomatik oturum başlığı desteği [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) içinde talep edilmiştir. | -| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) için [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327) içinde bir düzeltme önerilmiştir ancak henüz birleştirilmemiştir. | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) için [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327) içinde bir düzeltme önerilmiştir ancak henüz birleştirilmemiştir. | ## Kullanım limitleri diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index bbf58a721b5f..60ac0cf1eae6 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -97,26 +97,26 @@ OpenCode Go 适用于 [OpenCode](https://opencode.ai) 以及其他会产生类 除 OpenCode 外,以下客户端已通过验证,能够正常使用 OpenCode Go。但我们无法保证它们未来仍能正常使用。 -| 客户端 | 会话支持 | -| --- | --- | -| **Hermes** | 包含 [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) 的构建版本会在主要和辅助 OpenCode 请求中发送该请求头。此修复在 v0.21.0 发布后才合并,因此 v0.21.0 本身并不包含该修复。 | -| **Claude Code** | Go 能识别其原生会话请求头,无需额外封装来添加自定义请求头。 | -| **Codex** | Go 能识别其原生会话请求头。某些版本和代理配置仍会遗漏该请求头;转发请求时请保留会话请求头。 | -| **ZCode** | Go 能识别其原生会话请求头。我们[请求支持 `x-opencode-session` 的 issue](https://github.com/zai-org/feedback/issues/492) 仍处于开放状态,但已不再需要发送这一特定请求头。 | -| **Pi** | 当前构建版本会为 OpenCode 发送会话信息。请更新旧版安装。 | -| **jcode** | 请更新至 **v0.81.6 或更高版本**,其中包含[会话请求头修复](https://github.com/1jehuang/jcode/issues/1167)。 | +| 客户端 | 会话支持 | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | 包含 [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) 的构建版本会在主要和辅助 OpenCode 请求中发送该请求头。此修复在 v0.21.0 发布后才合并,因此 v0.21.0 本身并不包含该修复。 | +| **Claude Code** | Go 能识别其原生会话请求头,无需额外封装来添加自定义请求头。 | +| **Codex** | Go 能识别其原生会话请求头。某些版本和代理配置仍会遗漏该请求头;转发请求时请保留会话请求头。 | +| **ZCode** | Go 能识别其原生会话请求头。我们[请求支持 `x-opencode-session` 的 issue](https://github.com/zai-org/feedback/issues/492) 仍处于开放状态,但已不再需要发送这一特定请求头。 | +| **Pi** | 当前构建版本会为 OpenCode 发送会话信息。请更新旧版安装。 | +| **jcode** | 请更新至 **v0.81.6 或更高版本**,其中包含[会话请求头修复](https://github.com/1jehuang/jcode/issues/1167)。 | | **Kilo Code CLI** | 包含 [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) 的构建版本恢复了 OpenCode 会话请求头。此修复仅适用于 CLI,不适用于 VS Code 扩展。参见 [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723)。 | ### 已知存在问题的客户端 在我们调查的版本中,以下客户端缺少会话支持,或支持不完整。相关报告链接可用于跟踪修复进展和临时解决方案。 -| 客户端 | 状态与跟踪 | -| --- | --- | -| **DeepSeek Harness** | 某些模型调用路径会传递会话信息,但其他路径中缺失。我们能识别其原生请求头;剩余工作是在所有适配器中发送该请求头。参见[讨论 #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495)。 | -| **GitHub Copilot Chat** | [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) 已提出自动发送会话请求头的支持请求。 | -| **Kimi Code** | [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) 已提出自动发送会话请求头的支持请求。 | -| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) 已有拟议修复 [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327),但尚未合并。 | +| 客户端 | 状态与跟踪 | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | 某些模型调用路径会传递会话信息,但其他路径中缺失。我们能识别其原生请求头;剩余工作是在所有适配器中发送该请求头。参见[讨论 #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495)。 | +| **GitHub Copilot Chat** | [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) 已提出自动发送会话请求头的支持请求。 | +| **Kimi Code** | [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) 已提出自动发送会话请求头的支持请求。 | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) 已有拟议修复 [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327),但尚未合并。 | ## 使用限制 diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index 4cd2b8a9fd0b..e81efc392cdf 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -97,26 +97,26 @@ OpenCode Go 適用於 [OpenCode](https://opencode.ai) 以及其他會產生類 除了 OpenCode,以下用戶端已通過驗證,可正常使用 OpenCode Go。但我們無法保證它們未來仍能正常使用。 -| 用戶端 | 工作階段支援 | -| --- | --- | -| **Hermes** | 包含 [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) 的建置版本會在主要和輔助 OpenCode 請求中傳送該標頭。此修正是在 v0.21.0 發布後才合併,因此 v0.21.0 本身並未包含此修正。 | -| **Claude Code** | Go 可辨識其原生工作階段標頭,無須額外封裝來新增自訂標頭。 | -| **Codex** | Go 可辨識其原生工作階段標頭。部分版本和代理伺服器設定仍會遺漏該標頭;轉送請求時請保留工作階段標頭。 | -| **ZCode** | Go 可辨識其原生工作階段標頭。我們[請求支援 `x-opencode-session` 的 issue](https://github.com/zai-org/feedback/issues/492) 仍未關閉,但已不再需要傳送這個特定標頭。 | -| **Pi** | 目前的建置版本會為 OpenCode 傳送工作階段資訊。請更新舊版安裝。 | -| **jcode** | 請更新至 **v0.81.6 或更新版本**,其中包含[工作階段標頭修正](https://github.com/1jehuang/jcode/issues/1167)。 | +| 用戶端 | 工作階段支援 | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Hermes** | 包含 [PR #101864](https://github.com/NousResearch/hermes-agent/pull/101864) 的建置版本會在主要和輔助 OpenCode 請求中傳送該標頭。此修正是在 v0.21.0 發布後才合併,因此 v0.21.0 本身並未包含此修正。 | +| **Claude Code** | Go 可辨識其原生工作階段標頭,無須額外封裝來新增自訂標頭。 | +| **Codex** | Go 可辨識其原生工作階段標頭。部分版本和代理伺服器設定仍會遺漏該標頭;轉送請求時請保留工作階段標頭。 | +| **ZCode** | Go 可辨識其原生工作階段標頭。我們[請求支援 `x-opencode-session` 的 issue](https://github.com/zai-org/feedback/issues/492) 仍未關閉,但已不再需要傳送這個特定標頭。 | +| **Pi** | 目前的建置版本會為 OpenCode 傳送工作階段資訊。請更新舊版安裝。 | +| **jcode** | 請更新至 **v0.81.6 或更新版本**,其中包含[工作階段標頭修正](https://github.com/1jehuang/jcode/issues/1167)。 | | **Kilo Code CLI** | 包含 [PR #13752](https://github.com/Kilo-Org/kilocode/pull/13752) 的建置版本恢復了 OpenCode 工作階段標頭。此修正僅適用於 CLI,不適用於 VS Code 擴充套件。請參閱 [issue #13723](https://github.com/Kilo-Org/kilocode/issues/13723)。 | ### 已知有問題的用戶端 在我們調查的版本中,以下用戶端缺少工作階段支援,或支援不完整。相關回報連結可用來追蹤修正進度與暫時解決方式。 -| 用戶端 | 狀態與追蹤 | -| --- | --- | -| **DeepSeek Harness** | 部分模型呼叫路徑會傳遞工作階段資訊,但其他路徑中缺少這些資訊。我們可辨識其原生標頭;剩餘工作是在所有配接器中傳送該標頭。請參閱[討論 #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495)。 | -| **GitHub Copilot Chat** | [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) 已提出自動傳送工作階段標頭的支援請求。 | -| **Kimi Code** | [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) 已提出自動傳送工作階段標頭的支援請求。 | -| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) 已有修正提案 [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327),但尚未合併。 | +| 用戶端 | 狀態與追蹤 | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DeepSeek Harness** | 部分模型呼叫路徑會傳遞工作階段資訊,但其他路徑中缺少這些資訊。我們可辨識其原生標頭;剩餘工作是在所有配接器中傳送該標頭。請參閱[討論 #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495)。 | +| **GitHub Copilot Chat** | [VS Code issue #334186](https://github.com/microsoft/vscode/issues/334186) 已提出自動傳送工作階段標頭的支援請求。 | +| **Kimi Code** | [issue #3506](https://github.com/MoonshotAI/kimi-code/issues/3506) 已提出自動傳送工作階段標頭的支援請求。 | +| **MiMo Code** | [Issue #2317](https://github.com/XiaomiMiMo/MiMo-Code/issues/2317) 已有修正提案 [PR #2327](https://github.com/XiaomiMiMo/MiMo-Code/pull/2327),但尚未合併。 | ## 使用限制 From ac1758c0e6b8e7368be133e87e8e27a5f60dec04 Mon Sep 17 00:00:00 2001 From: Emrick Date: Wed, 9 Sep 2026 00:20:22 +0800 Subject: [PATCH 053/129] fix: preserve Bedrock DeepSeek model ids (#34441) Co-authored-by: yeqisong --- .../src/plugin/provider/amazon-bedrock.ts | 4 +- .../plugin/provider-amazon-bedrock.test.ts | 7 +++ packages/opencode/src/provider/provider.ts | 6 ++- .../test/provider/amazon-bedrock.test.ts | 43 +++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/core/src/plugin/provider/amazon-bedrock.ts b/packages/core/src/plugin/provider/amazon-bedrock.ts index 0995cf1c1724..1fb625c93612 100644 --- a/packages/core/src/plugin/provider/amazon-bedrock.ts +++ b/packages/core/src/plugin/provider/amazon-bedrock.ts @@ -13,13 +13,15 @@ type MantleSDK = { // specific model/region combinations. Keep the mapping narrow and avoid // double-prefixing model IDs that models.dev already marks as global/us/eu/etc. function resolveModelID(modelID: string, region: string | undefined) { + if (modelID.startsWith("arn:")) return modelID + const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."] if (crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))) return modelID const resolvedRegion = region ?? "us-east-1" const regionPrefix = resolvedRegion.split("-")[0] if (regionPrefix === "us") { - const requiresPrefix = ["nova-micro", "nova-lite", "nova-pro", "nova-premier", "nova-2", "claude", "deepseek"].some( + const requiresPrefix = ["nova-micro", "nova-lite", "nova-pro", "nova-premier", "nova-2", "claude", "deepseek.r1"].some( (item) => modelID.includes(item), ) if (requiresPrefix && !resolvedRegion.startsWith("us-gov")) return `${regionPrefix}.${modelID}` diff --git a/packages/core/test/plugin/provider-amazon-bedrock.test.ts b/packages/core/test/plugin/provider-amazon-bedrock.test.ts index 5d24879e492b..9ddcd127dcb3 100644 --- a/packages/core/test/plugin/provider-amazon-bedrock.test.ts +++ b/packages/core/test/plugin/provider-amazon-bedrock.test.ts @@ -528,6 +528,13 @@ describe("AmazonBedrockPlugin", () => { { region: "us-east-1", modelID: "amazon.nova-2-lite-v1:0", expected: "us.amazon.nova-2-lite-v1:0" }, { region: "us-east-1", modelID: "anthropic.claude-sonnet-4-5", expected: "us.anthropic.claude-sonnet-4-5" }, { region: "us-east-1", modelID: "deepseek.r1-v1:0", expected: "us.deepseek.r1-v1:0" }, + { region: "us-east-1", modelID: "us.deepseek.r1-v1:0", expected: "us.deepseek.r1-v1:0" }, + { region: "us-east-1", modelID: "deepseek.v3.2", expected: "deepseek.v3.2" }, + { + region: "us-east-1", + modelID: "arn:aws:bedrock:us-east-1::foundation-model/deepseek.v3.2", + expected: "arn:aws:bedrock:us-east-1::foundation-model/deepseek.v3.2", + }, { region: "us-gov-west-1", modelID: "anthropic.claude-sonnet-4-5", expected: "anthropic.claude-sonnet-4-5" }, { region: "us-east-1", modelID: "cohere.command-r-plus-v1:0", expected: "cohere.command-r-plus-v1:0" }, { region: "eu-west-1", modelID: "anthropic.claude-sonnet-4-5", expected: "eu.anthropic.claude-sonnet-4-5" }, diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 72d5a7a59382..867ef2ca2fa8 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -376,6 +376,10 @@ function custom(dep: CustomDep): Record { // Skip region prefixing if model already has a cross-region inference profile prefix // Models from models.dev may already include prefixes like us., eu., global., etc. + if (modelID.startsWith("arn:")) { + return sdk.languageModel(modelID) + } + const crossRegionPrefixes = ["global.", "us.", "eu.", "jp.", "apac.", "au."] if (crossRegionPrefixes.some((prefix) => modelID.startsWith(prefix))) { return sdk.languageModel(modelID) @@ -398,7 +402,7 @@ function custom(dep: CustomDep): Record { "nova-premier", "nova-2", "claude", - "deepseek", + "deepseek.r1", ].some((m) => modelID.includes(m)) const isGovCloud = region.startsWith("us-gov") if (modelRequiresPrefix && !isGovCloud) { diff --git a/packages/opencode/test/provider/amazon-bedrock.test.ts b/packages/opencode/test/provider/amazon-bedrock.test.ts index 6e677631c40b..e4654ddb3d7a 100644 --- a/packages/opencode/test/provider/amazon-bedrock.test.ts +++ b/packages/opencode/test/provider/amazon-bedrock.test.ts @@ -229,6 +229,49 @@ it.instance( { config: { provider: { "amazon-bedrock": { options: { region: "us-east-1" } } } } }, ) +it.instance( + "Bedrock: preserves explicit DeepSeek model identifiers", + () => + Effect.gen(function* () { + yield* set("AWS_BEARER_TOKEN_BEDROCK", "test-bearer-token") + const provider = yield* Provider.Service + const deepseek = yield* provider.getLanguage( + yield* provider.getModel(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("deepseek.v3.2")), + ) + const r1 = yield* provider.getLanguage( + yield* provider.getModel(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("deepseek.r1-v1:0")), + ) + const profile = yield* provider.getLanguage( + yield* provider.getModel(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("us.deepseek.r1-v1:0")), + ) + const arn = yield* provider.getLanguage( + yield* provider.getModel(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("deepseek-v3-2-arn")), + ) + expect((deepseek as { modelId: string }).modelId).toBe("deepseek.v3.2") + expect((r1 as { modelId: string }).modelId).toBe("us.deepseek.r1-v1:0") + expect((profile as { modelId: string }).modelId).toBe("us.deepseek.r1-v1:0") + expect((arn as { modelId: string }).modelId).toBe("arn:aws:bedrock:us-east-1::foundation-model/deepseek.v3.2") + }), + { + config: { + provider: { + "amazon-bedrock": { + options: { region: "us-east-1" }, + models: { + "deepseek.v3.2": { name: "DeepSeek V3.2" }, + "deepseek.r1-v1:0": { name: "DeepSeek R1" }, + "us.deepseek.r1-v1:0": { name: "DeepSeek R1 US" }, + "deepseek-v3-2-arn": { + id: "arn:aws:bedrock:us-east-1::foundation-model/deepseek.v3.2", + name: "DeepSeek V3.2 ARN", + }, + }, + }, + }, + }, + }, +) + // Cross-region inference profile prefix handling. // Models from models.dev may come with prefixes already (e.g. us., eu., global.). // These should NOT be double-prefixed when passed to the SDK. From dff8fbc149fb7492e4f07b713ac31ea70d9a541c Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Tue, 8 Sep 2026 16:21:49 +0000 Subject: [PATCH 054/129] chore: generate --- packages/core/src/plugin/provider/amazon-bedrock.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/core/src/plugin/provider/amazon-bedrock.ts b/packages/core/src/plugin/provider/amazon-bedrock.ts index 1fb625c93612..7b830796d69f 100644 --- a/packages/core/src/plugin/provider/amazon-bedrock.ts +++ b/packages/core/src/plugin/provider/amazon-bedrock.ts @@ -21,9 +21,15 @@ function resolveModelID(modelID: string, region: string | undefined) { const resolvedRegion = region ?? "us-east-1" const regionPrefix = resolvedRegion.split("-")[0] if (regionPrefix === "us") { - const requiresPrefix = ["nova-micro", "nova-lite", "nova-pro", "nova-premier", "nova-2", "claude", "deepseek.r1"].some( - (item) => modelID.includes(item), - ) + const requiresPrefix = [ + "nova-micro", + "nova-lite", + "nova-pro", + "nova-premier", + "nova-2", + "claude", + "deepseek.r1", + ].some((item) => modelID.includes(item)) if (requiresPrefix && !resolvedRegion.startsWith("us-gov")) return `${regionPrefix}.${modelID}` return modelID } From 5cd8e68fdd72b27818d26d168b9c7a06b359567e Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:27:13 -0500 Subject: [PATCH 055/129] feat(opencode): port Astra system prompt from v2 (#48057) --- .../opencode/src/session/prompt/gpt-astra.txt | 46 +++++++++++++++++++ packages/opencode/src/session/system.ts | 2 + 2 files changed, 48 insertions(+) create mode 100644 packages/opencode/src/session/prompt/gpt-astra.txt diff --git a/packages/opencode/src/session/prompt/gpt-astra.txt b/packages/opencode/src/session/prompt/gpt-astra.txt new file mode 100644 index 000000000000..1dba79282ac9 --- /dev/null +++ b/packages/opencode/src/session/prompt/gpt-astra.txt @@ -0,0 +1,46 @@ +You are an AI agent powered by OpenCode, a coding agent harness. Help the user accomplish their goals using the tools you have available. + +# Harness +- Responses are rendered as GitHub-flavored Markdown. +- `` blocks are harness instructions, not user-authored content. Read and follow them. +- Prefer parallelizing independent tool calls. +- Do not use a skill based solely on keywords, superficial relevance, or its availability. Avoid re-reading skills already available in the conversation unless needed. +- Prefer dedicated tools over shell commands; fall back to the shell when a tool cannot do what you need. +- Do not chain shell commands with separators like `echo "====";` or `printf '---'`; the output becomes noisy in a way that makes the user's side of the conversation worse. + +# Communication + +State the main point clearly and early. Keep responses clear and concise, and avoid unnecessary technical jargon. Use only as much structure as needed, and include technical detail only when it helps the conversation. Use clear file paths when referring to files. + +When describing your work, avoid adding what you won't do, what will remain unchanged, or how you'll separate or categorize results. Do not introduce unprompted alternatives through framing such as "X, not Y" or "This isn't about X. It's about Y." + +## Autonomy + +Infer the user's intent and your task scope from their instructions and the prior conversation context. You should bias towards action and carry out the user's intended task until it is completed. If the intent is unclear, progress towards the goal using the available information and ask for clarification while continuing independent work when possible. + +When the user's prompt indicates a request for action, such as "can you...", "I want to...", "help me..." and similar expressions, treat these as instructions to take action. Do not stop at acknowledging capability (e.g. "Yes…"), proposing a plan, or offering to continue. Do not settle for a partial or "helpful enough" solution to save time, effort, or tokens. Continue until the user's intended goal is fulfilled, even when it requires sustained work. + +## Intermediate Commentary + +As you work, you send messages to the commentary channel. These are how you collaborate with the user while you work: stating assumptions and providing updates. Keep them concise and quickly scannable, and send them only when they add real information, such as a discovery, a tradeoff, or a blocker. Do not narrate routine reads, searches, or edits. + +By default, treat new messages received during ongoing work as steering the active task rather than replacing it. Incorporate corrections and constraints, and answer questions briefly in commentary before continuing. Replace the task only when the user clearly cancels it or requests an incompatible objective. + +Do not put a final response, such as a blocking or clarifying question, in the commentary channel. The final answer must always be fully self-contained. + +## Final Answer + +In your final answer back to the user, focus on the most important information. + +# Working in codebases + +- Keep changes consistent with the structure, naming, style, and patterns of the surrounding code. +- Treat unfamiliar files or changes as potential user work and investigate before deleting or overwriting them. +- Do not introduce unsolicited warnings, disclaimers, approval flows, or safety/compliance checklists due to hypothetical risk. +- Do not write tests for reversible, low-impact changes or that mirror the implementation. If you do choose to verify your work with tests, make sure that the tests are meaningful and necessary to verify implementation. +- Run tests appropriate to the change and complete required checks. Once those pass, broaden or repeat testing only when new changes, failures, or unresolved concerns justify it; otherwise, continue toward completing the task. + + +# Delegation + +Do not spawn subagents unless the user or applicable AGENTS.md/skill instructions explicitly ask for subagents, delegation, or parallel agent work. diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index d0c608b203f6..61ab74d9f158 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -8,6 +8,7 @@ import PROMPT_DEFAULT from "./prompt/default.txt" import PROMPT_BEAST from "./prompt/beast.txt" import PROMPT_GEMINI from "./prompt/gemini.txt" import PROMPT_GPT from "./prompt/gpt.txt" +import PROMPT_ASTRA from "./prompt/gpt-astra.txt" import PROMPT_KIMI from "./prompt/kimi.txt" import PROMPT_META from "./prompt/meta.txt" @@ -32,6 +33,7 @@ export function provider(model: Provider.Model) { if (model.api.id.includes("gpt-4") || model.api.id.includes("o1") || model.api.id.includes("o3")) return [PROMPT_BEAST] if (model.api.id.includes("gpt")) { + if (model.api.id.includes("gpt-6")) return [PROMPT_ASTRA] if (model.api.id.includes("codex")) { return [PROMPT_CODEX] } From 830d5eb5354874105cc31599635a80c1662609e8 Mon Sep 17 00:00:00 2001 From: opencode Date: Wed, 9 Sep 2026 03:34:25 +0000 Subject: [PATCH 056/129] sync release versions for v1.18.30 --- bun.lock | 56 ++++++++++----------- packages/app/package.json | 2 +- packages/cli/package.json | 2 +- packages/codemode/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/console/support/package.json | 2 +- packages/core/package.json | 2 +- packages/desktop/package.json | 2 +- packages/effect-drizzle-sqlite/package.json | 2 +- packages/effect-sqlite-node/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/function/package.json | 2 +- packages/http-recorder/package.json | 2 +- packages/llm/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/server/package.json | 2 +- packages/session-ui/package.json | 2 +- packages/slack/package.json | 2 +- packages/stats/app/package.json | 2 +- packages/stats/core/package.json | 2 +- packages/stats/server/package.json | 2 +- packages/tui/package.json | 2 +- packages/ui/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 30 files changed, 57 insertions(+), 57 deletions(-) diff --git a/bun.lock b/bun.lock index 0efe1b76b05f..efe01bf957f3 100644 --- a/bun.lock +++ b/bun.lock @@ -29,7 +29,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@corvu/drawer": "catalog:", "@dnd-kit/abstract": "0.5.0", @@ -96,7 +96,7 @@ }, "packages/cli": { "name": "@opencode-ai/cli", - "version": "1.18.29", + "version": "1.18.30", "bin": { "lildax": "./bin/lildax.cjs", }, @@ -144,7 +144,7 @@ }, "packages/codemode": { "name": "@opencode-ai/codemode", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "acorn": "8.15.0", "effect": "catalog:", @@ -158,7 +158,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -195,7 +195,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -222,7 +222,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@ai-sdk/anthropic": "3.0.82", "@ai-sdk/openai": "3.0.48", @@ -245,7 +245,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -269,7 +269,7 @@ }, "packages/console/support": { "name": "@opencode-ai/console-support", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@opencode-ai/console-core": "workspace:*", @@ -289,7 +289,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "1.18.29", + "version": "1.18.30", "bin": { "opencode": "./bin/opencode", }, @@ -383,7 +383,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@zip.js/zip.js": "2.7.62", "drizzle-orm": "catalog:", @@ -437,7 +437,7 @@ }, "packages/effect-drizzle-sqlite": { "name": "@opencode-ai/effect-drizzle-sqlite", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -451,7 +451,7 @@ }, "packages/effect-sqlite-node": { "name": "@opencode-ai/effect-sqlite-node", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "effect": "catalog:", }, @@ -463,7 +463,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@hono/standard-validator": "catalog:", "@opencode-ai/core": "workspace:*", @@ -495,7 +495,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -511,7 +511,7 @@ }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@effect/platform-node": "4.0.0-beta.83", "@effect/platform-node-shared": "4.0.0-beta.83", @@ -542,7 +542,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@opencode-ai/schema": "workspace:*", "@smithy/eventstream-codec": "4.2.14", @@ -561,7 +561,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.18.29", + "version": "1.18.30", "bin": { "opencode": "./bin/opencode", }, @@ -692,7 +692,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "workspace:*", @@ -768,7 +768,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "cross-spawn": "catalog:", }, @@ -783,7 +783,7 @@ }, "packages/server": { "name": "@opencode-ai/server", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/protocol": "workspace:*", @@ -798,7 +798,7 @@ }, "packages/session-ui": { "name": "@opencode-ai/session-ui", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz", @@ -838,7 +838,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -851,7 +851,7 @@ }, "packages/stats/app": { "name": "@opencode-ai/stats-app", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@ibm/plex": "6.4.1", "@kobalte/core": "catalog:", @@ -878,7 +878,7 @@ }, "packages/stats/core": { "name": "@opencode-ai/stats-core", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@aws-sdk/client-athena": "3.933.0", "@planetscale/database": "1.19.0", @@ -897,7 +897,7 @@ }, "packages/stats/server": { "name": "@opencode-ai/stats-server", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@aws-sdk/client-firehose": "3.933.0", "@effect/platform-node": "catalog:", @@ -939,7 +939,7 @@ }, "packages/tui": { "name": "@opencode-ai/tui", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@opencode-ai/core": "workspace:*", "@opencode-ai/plugin": "workspace:*", @@ -966,7 +966,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@kobalte/core": "catalog:", "@pierre/diffs": "catalog:", @@ -1017,7 +1017,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.18.29", + "version": "1.18.30", "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 4accd2c499e1..17b356ed6087 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.18.29", + "version": "1.18.30", "description": "", "type": "module", "exports": { diff --git a/packages/cli/package.json b/packages/cli/package.json index dc70e71ef439..f6dd6bf831f3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/cli", - "version": "1.18.29", + "version": "1.18.30", "type": "module", "license": "MIT", "bin": { diff --git a/packages/codemode/package.json b/packages/codemode/package.json index 66f983ad82c9..c516b3a98589 100644 --- a/packages/codemode/package.json +++ b/packages/codemode/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/codemode", - "version": "1.18.29", + "version": "1.18.30", "description": "Effect-native confined code execution over schema-described tools", "private": true, "type": "module", diff --git a/packages/console/app/package.json b/packages/console/app/package.json index f5d06e24d772..3651594fd764 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.18.29", + "version": "1.18.30", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 23aade046bde..19aade616d72 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.18.29", + "version": "1.18.30", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 3a06de763e94..d77be8cb6a5f 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.18.29", + "version": "1.18.30", "$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 cb4ceb404d54..e35574b3e9b9 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.18.29", + "version": "1.18.30", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/console/support/package.json b/packages/console/support/package.json index 815bfd644ae6..75e8e0714716 100644 --- a/packages/console/support/package.json +++ b/packages/console/support/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-support", - "version": "1.18.29", + "version": "1.18.30", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index daa4c4e91e3d..cc782f512e37 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.29", + "version": "1.18.30", "name": "@opencode-ai/core", "type": "module", "license": "MIT", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index e652a6a188df..7c55897b748a 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.18.29", + "version": "1.18.30", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/effect-drizzle-sqlite/package.json b/packages/effect-drizzle-sqlite/package.json index 0ffa670143c6..35cc50f97d47 100644 --- a/packages/effect-drizzle-sqlite/package.json +++ b/packages/effect-drizzle-sqlite/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.29", + "version": "1.18.30", "name": "@opencode-ai/effect-drizzle-sqlite", "type": "module", "license": "MIT", diff --git a/packages/effect-sqlite-node/package.json b/packages/effect-sqlite-node/package.json index 819f7c6d10e4..19d0652c1a65 100644 --- a/packages/effect-sqlite-node/package.json +++ b/packages/effect-sqlite-node/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.29", + "version": "1.18.30", "name": "@opencode-ai/effect-sqlite-node", "type": "module", "license": "MIT", diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 038a958579bb..9d61ae5eacfd 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.18.29", + "version": "1.18.30", "private": true, "type": "module", "license": "MIT", diff --git a/packages/function/package.json b/packages/function/package.json index b8dfeca3d2c8..7d9bc6548b66 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.18.29", + "version": "1.18.30", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/http-recorder/package.json b/packages/http-recorder/package.json index c12a30fe0c93..cc8ae63655f9 100644 --- a/packages/http-recorder/package.json +++ b/packages/http-recorder/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.29", + "version": "1.18.30", "name": "@opencode-ai/http-recorder", "description": "Record and replay Effect HTTP client traffic with deterministic cassettes", "type": "module", diff --git a/packages/llm/package.json b/packages/llm/package.json index 7e541f197337..79471820d654 100644 --- a/packages/llm/package.json +++ b/packages/llm/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.29", + "version": "1.18.30", "name": "@opencode-ai/llm", "type": "module", "license": "MIT", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index d1aca264d548..c7c467037d10 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.18.29", + "version": "1.18.30", "name": "opencode", "type": "module", "license": "MIT", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 6af058c72090..d06914cdc8d2 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.18.29", + "version": "1.18.30", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index e6fa0addcc14..9777184dfe38 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.18.29", + "version": "1.18.30", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 3c016338dc33..04cb235ff640 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/server", - "version": "1.18.29", + "version": "1.18.30", "private": true, "type": "module", "license": "MIT", diff --git a/packages/session-ui/package.json b/packages/session-ui/package.json index 0c1c656bb503..7db27630da53 100644 --- a/packages/session-ui/package.json +++ b/packages/session-ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/session-ui", - "version": "1.18.29", + "version": "1.18.30", "private": true, "type": "module", "license": "MIT", diff --git a/packages/slack/package.json b/packages/slack/package.json index deb856fd5c6e..f1aa4602ba6d 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.18.29", + "version": "1.18.30", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/stats/app/package.json b/packages/stats/app/package.json index 5340918954d4..20d56aa59676 100644 --- a/packages/stats/app/package.json +++ b/packages/stats/app/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-app", - "version": "1.18.29", + "version": "1.18.30", "private": true, "type": "module", "license": "MIT", diff --git a/packages/stats/core/package.json b/packages/stats/core/package.json index 3da366c56760..d6ffa5fd65f4 100644 --- a/packages/stats/core/package.json +++ b/packages/stats/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-core", - "version": "1.18.29", + "version": "1.18.30", "private": true, "type": "module", "license": "MIT", diff --git a/packages/stats/server/package.json b/packages/stats/server/package.json index 567ba2c21b45..27f5e23ec0fe 100644 --- a/packages/stats/server/package.json +++ b/packages/stats/server/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/stats-server", - "version": "1.18.29", + "version": "1.18.30", "private": true, "type": "module", "license": "MIT", diff --git a/packages/tui/package.json b/packages/tui/package.json index e57a9904d0dd..4b9b95b35e68 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/tui", - "version": "1.18.29", + "version": "1.18.30", "private": true, "type": "module", "license": "MIT", diff --git a/packages/ui/package.json b/packages/ui/package.json index eaa9b5a3f3ef..28dbce9ecabc 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.18.29", + "version": "1.18.30", "type": "module", "license": "MIT", "repository": { diff --git a/packages/web/package.json b/packages/web/package.json index 2401a7fb71cf..623f08dfca2e 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.18.29", + "version": "1.18.30", "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 e1408de69851..5c43de80758e 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.18.29", + "version": "1.18.30", "publisher": "sst-dev", "repository": { "type": "git", From f69beceaffca94bed05a7669af93602125c37248 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 9 Sep 2026 23:41:56 +0800 Subject: [PATCH 057/129] docs(go): update GLM-5.3-Flash allowance (#48130) --- .../app/src/component/limits-graph.tsx | 86 +++---------------- packages/console/app/src/i18n/ar.ts | 1 - packages/console/app/src/i18n/br.ts | 1 - packages/console/app/src/i18n/da.ts | 1 - packages/console/app/src/i18n/de.ts | 1 - packages/console/app/src/i18n/en.ts | 1 - packages/console/app/src/i18n/es.ts | 1 - packages/console/app/src/i18n/fr.ts | 1 - packages/console/app/src/i18n/it.ts | 1 - packages/console/app/src/i18n/ja.ts | 1 - packages/console/app/src/i18n/ko.ts | 1 - packages/console/app/src/i18n/no.ts | 1 - packages/console/app/src/i18n/pl.ts | 1 - packages/console/app/src/i18n/ru.ts | 1 - packages/console/app/src/i18n/th.ts | 1 - packages/console/app/src/i18n/tr.ts | 1 - packages/console/app/src/i18n/uk.ts | 1 - packages/console/app/src/i18n/zh.ts | 1 - packages/console/app/src/i18n/zht.ts | 1 - packages/console/app/src/routes/go/index.tsx | 6 -- 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 +- 38 files changed, 50 insertions(+), 132 deletions(-) diff --git a/packages/console/app/src/component/limits-graph.tsx b/packages/console/app/src/component/limits-graph.tsx index 63ffeafaa7cc..aa9744ecbb8a 100644 --- a/packages/console/app/src/component/limits-graph.tsx +++ b/packages/console/app/src/component/limits-graph.tsx @@ -1,13 +1,9 @@ import { For, createSignal, onCleanup, onMount } from "solid-js" import { useI18n } from "~/context/i18n" -import { RollingNumber } from "./rolling-number" export function LimitsGraph(props: { href: string }) { let root!: HTMLElement const [visible, setVisible] = createSignal(false) - const [boosted, setBoosted] = createSignal(false) - const [promoted, setPromoted] = createSignal([]) - let timer: ReturnType | undefined const i18n = useI18n() @@ -15,14 +11,10 @@ export function LimitsGraph(props: { href: string }) { const motion = window.matchMedia("(prefers-reduced-motion: reduce)") const finish = () => { if (!motion.matches) return - clearTimeout(timer) setVisible(true) - setBoosted(true) - setPromoted(bonuses.map((model) => model.id)) } motion.addEventListener("change", finish) onCleanup(() => { - clearTimeout(timer) motion.removeEventListener("change", finish) }) if (motion.matches) return finish() @@ -46,18 +38,17 @@ export function LimitsGraph(props: { href: string }) { { id: "grok-4.6", name: "Grok 4.6", req: 169 }, { id: "hy4-preview", name: "Hy4 preview", req: 1350 }, { id: "gpt-5.6-luna", name: "GPT 5.6 Luna", req: 2050 }, - { id: "glm-5.3-flash", name: "GLM-5.3-Flash", req: 3160, baseReq: 1580, bonus: "2x usage" }, { id: "minimax-m3", name: "MiniMax M3", req: 3200 }, { id: "qwen3.7-plus", name: "Qwen3.7 Plus", req: 4300 }, { id: "hy3", name: "Hy3", req: 4300 }, { id: "qwen3.8-flash", name: "Qwen3.8 Flash", req: 5400 }, + { id: "glm-5.3-flash", name: "GLM-5.3-Flash", req: 6320 }, { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 7600 }, { id: "longcat-2.0", name: "LongCat-2.0", req: 11400 }, { id: "omen-alpha", name: "Omen Alpha", req: 11600 }, { id: "mimo-v2.5", name: "MiMo-V2.5", req: 30100 }, { id: "muse-spark-1.3-contributor", name: "Muse Spark 1.3 Contributor", req: 45300, edge: true }, ].map((model, index) => ({ ...model, d: `${50 + index * 25}ms` })) - const bonuses = graph.filter((model) => model.baseReq) const w = 1040 const chartW = 720 @@ -102,34 +93,13 @@ export function LimitsGraph(props: { href: string }) { const py = (n: number) => `${(n / h) * 100}%` const lx = px(left - 16) const ty = py(h - 18) - const timing = () => { - const style = getComputedStyle(root) - return { - duration: Number.parseFloat(style.getPropertyValue("--bonus-duration")), - easing: style.getPropertyValue("--spring-easing").trim(), - spinEasing: style.getPropertyValue("--digit-easing").trim(), - } - } return (
    { - if (!(event.target instanceof SVGElement) || event.animationName !== "go-graph-reveal") return - if (event.target.hasAttribute("data-stage-end") && !boosted()) { - const duration = Number.parseFloat(getComputedStyle(root).getPropertyValue("--reveal-duration")) - timer = setTimeout(() => setBoosted(true), duration * 0.6) - return - } - if (event.target.dataset.animate !== "bonus") return - const model = event.target.dataset.model - if (!model) return - setPromoted((current) => [...current, model]) - }} >
    {(m, i) => ( - <> - - {m.baseReq && ( - - )} - + )} @@ -213,25 +167,14 @@ export function LimitsGraph(props: { href: string }) { data-model={m.id} data-edge={"edge" in m ? "" : undefined} data-infinite={"infinite" in m ? "" : undefined} - data-promo={m.baseReq ? "" : undefined} style={{ - "--x": px("infinite" in m ? infiniteX : x(ratio(m.baseReq ?? m.req))), + "--x": px("infinite" in m ? infiniteX : x(ratio(m.req))), "--y": py(gy(i())), "--d": m.d, - "--bonus-delay": `${Math.max(0, bonuses.indexOf(m)) * 60}ms`, - "--travel": `${"infinite" in m ? 0 : ((x(ratio(m.req)) - x(ratio(m.baseReq ?? m.req))) / w) * 100}cqw`, }} > - {!("infinite" in m) && m.baseReq ? ( - - ) : ( - {"infinite" in m ? "\u221e" : m.req.toLocaleString()} - )} + {"infinite" in m ? "\u221e" : m.req.toLocaleString()} {m.name} {m.id === "muse-spark-1.3-contributor" && ( @@ -244,7 +187,6 @@ export function LimitsGraph(props: { href: string }) { )} {"infinite" in m && ({i18n.t("go.graph.limitedTime")})} - {"bonus" in m && {m.bonus}} )} diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index 54547d776216..2d792158741f 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -256,7 +256,6 @@ export const dict = { "zen.privacy.exceptionsLink": "الاستثناءات التالية", "go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع", - "go.banner.text": "يحصل GLM-5.3-Flash على حدود استخدام مضاعفة لفترة محدودة", "go.meta.description": "يبلغ سعر Go ‏$10/شهر، مع حدود استخدام سخية ووصول موثوق إلى نماذج البرمجة الرائدة.", "go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع", "go.hero.body": diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index a0ead2369689..ded260326811 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -260,7 +260,6 @@ export const dict = { "zen.privacy.exceptionsLink": "seguintes exceções", "go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos", - "go.banner.text": "GLM-5.3-Flash tem limites de uso 2x maiores por tempo limitado", "go.meta.description": "O Go custa $10/mês, com limites generosos de uso e acesso confiável aos principais modelos de codificação.", "go.hero.title": "Modelos de codificação de baixo custo para todos", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index 3f659a2181ca..de296cb0f555 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -258,7 +258,6 @@ export const dict = { "zen.privacy.exceptionsLink": "følgende undtagelser", "go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle", - "go.banner.text": "GLM-5.3-Flash får fordoblet brugsgrænse i en begrænset periode", "go.meta.description": "Go koster $10/måned, med generøse brugsgrænser og pålidelig adgang til førende kodningsmodeller.", "go.hero.title": "Kodningsmodeller til lav pris for alle", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index dbb97d89a88d..898a9d87a33a 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -260,7 +260,6 @@ export const dict = { "zen.privacy.exceptionsLink": "folgenden Ausnahmen", "go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle", - "go.banner.text": "GLM-5.3-Flash erhält für begrenzte Zeit 2x Nutzungslimits", "go.meta.description": "Go kostet $10/Monat, mit großzügigen Nutzungslimits und zuverlässigem Zugang zu führenden Coding-Modellen.", "go.hero.title": "Kostengünstige Coding-Modelle für alle", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index e39b42eb761f..1031a72e706a 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -256,7 +256,6 @@ export const dict = { "zen.privacy.exceptionsLink": "following exceptions", "go.title": "OpenCode Go | Low cost coding models for everyone", - "go.banner.text": "GLM-5.3-Flash gets 2× usage limits for a limited time", "go.meta.description": "Go costs $10/month, with generous usage limits and reliable access to leading coding models.", "go.hero.title": "Low cost coding models for everyone", "go.hero.body": diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 47fc8a446139..4e3d5cd41cb1 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -261,7 +261,6 @@ export const dict = { "zen.privacy.exceptionsLink": "siguientes excepciones", "go.title": "OpenCode Go | Modelos de programación de bajo coste para todos", - "go.banner.text": "GLM-5.3-Flash tiene límites de uso 2x mayores por tiempo limitado", "go.meta.description": "Go cuesta 10 $/mes, con límites de uso generosos y acceso fiable a modelos de programación líderes.", "go.hero.title": "Modelos de programación de bajo coste para todos", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index e5a913315177..fa1de59ee967 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -262,7 +262,6 @@ export const dict = { "zen.privacy.exceptionsLink": "exceptions suivantes", "go.title": "OpenCode Go | Modèles de code à faible coût pour tous", - "go.banner.text": "GLM-5.3-Flash bénéficie de limites d’utilisation 2x supérieures pour une durée limitée", "go.meta.description": "Go coûte 10 $/mois, avec des limites d'utilisation généreuses et un accès fiable aux principaux modèles de codage.", "go.hero.title": "Modèles de code à faible coût pour tous", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 9b4d200043e5..0c81b6e94e14 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -258,7 +258,6 @@ export const dict = { "zen.privacy.exceptionsLink": "seguenti eccezioni", "go.title": "OpenCode Go | Modelli di coding a basso costo per tutti", - "go.banner.text": "GLM-5.3-Flash offre limiti di utilizzo 2x superiori per un periodo limitato", "go.meta.description": "Go costa $10/mese, con limiti di utilizzo generosi e un accesso affidabile ai principali modelli di coding.", "go.hero.title": "Modelli di coding a basso costo per tutti", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index de390eb6df34..534f8a95e948 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -257,7 +257,6 @@ export const dict = { "zen.privacy.exceptionsLink": "以下の例外", "go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル", - "go.banner.text": "GLM-5.3-Flashの利用上限が期間限定で2倍に", "go.meta.description": "Goは月額$10で、主要なコーディングモデルへのゆとりある利用上限と安定したアクセスを提供します。", "go.hero.title": "すべての人のための低価格なコーディングモデル", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index ea4a0a5da717..f480675c9f48 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -254,7 +254,6 @@ export const dict = { "zen.privacy.exceptionsLink": "다음 예외", "go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델", - "go.banner.text": "GLM-5.3-Flash 사용 한도가 한시적으로 2배 확대됩니다", "go.meta.description": "Go는 월 $10이며, 넉넉한 사용 한도와 주요 코딩 모델에 대한 안정적인 액세스를 제공합니다.", "go.hero.title": "모두를 위한 저비용 코딩 모델", "go.hero.body": diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index 606f613dcd11..95fd5f5e9c42 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -258,7 +258,6 @@ export const dict = { "zen.privacy.exceptionsLink": "følgende unntak", "go.title": "OpenCode Go | Rimelige kodemodeller for alle", - "go.banner.text": "GLM-5.3-Flash får 2x bruksgrense i en begrenset periode", "go.meta.description": "Go koster $10/måned, med sjenerøse bruksgrenser og pålitelig tilgang til ledende kodemodeller.", "go.hero.title": "Rimelige kodemodeller for alle", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index 30614954c052..b27528fc23cf 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -259,7 +259,6 @@ export const dict = { "zen.privacy.exceptionsLink": "następującymi wyjątkami", "go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego", - "go.banner.text": "GLM-5.3-Flash oferuje 2x wyższe limity użycia przez ograniczony czas", "go.meta.description": "Go kosztuje $10/miesiąc, oferując hojne limity użycia i niezawodny dostęp do wiodących modeli do kodowania.", "go.hero.title": "Niskokosztowe modele do kodowania dla każdego", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 040c84c635e1..d2154ef83012 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -262,7 +262,6 @@ export const dict = { "zen.privacy.exceptionsLink": "следующими исключениями", "go.title": "OpenCode Go | Недорогие модели для кодинга для всех", - "go.banner.text": "GLM-5.3-Flash получает 2x лимиты использования на ограниченное время", "go.meta.description": "Go стоит $10/месяц и предлагает щедрые лимиты использования и надежный доступ к ведущим моделям для кодинга.", "go.hero.title": "Недорогие модели для кодинга для всех", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index 16219983c969..9b1f78f52a45 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -257,7 +257,6 @@ export const dict = { "zen.privacy.exceptionsLink": "ข้อยกเว้นดังนี้", "go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน", - "go.banner.text": "GLM-5.3-Flash เพิ่มโควตาการใช้งานเป็น 2 เท่าในช่วงเวลาจำกัด", "go.meta.description": "Go มีราคา $10/เดือน พร้อมขีดจำกัดการใช้งานที่เอื้อเฟื้อและการเข้าถึงโมเดลเขียนโค้ดชั้นนำอย่างเชื่อถือได้", "go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index b43320093ec5..49f7a788cb7b 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -260,7 +260,6 @@ export const dict = { "zen.privacy.exceptionsLink": "aşağıdaki istisnalar", "go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri", - "go.banner.text": "GLM-5.3-Flash sınırlı bir süre için 2x kullanım limiti sunuyor", "go.meta.description": "Go ayda 10$'dır; cömert kullanım limitleri ve önde gelen kodlama modellerine güvenilir erişim sunar.", "go.hero.title": "Herkes için düşük maliyetli kodlama modelleri", diff --git a/packages/console/app/src/i18n/uk.ts b/packages/console/app/src/i18n/uk.ts index 8b2aafc7ab98..d3002ad84854 100644 --- a/packages/console/app/src/i18n/uk.ts +++ b/packages/console/app/src/i18n/uk.ts @@ -258,7 +258,6 @@ export const dict = { "zen.privacy.exceptionsLink": "такими винятками", "go.title": "OpenCode Go | Недорогі моделі кодування для всіх", - "go.banner.text": "GLM-5.3-Flash отримує 2x ліміти використання протягом обмеженого часу", "go.meta.description": "Go коштує $10/місяць, зі щедрими лімітами використання та надійним доступом до провідних моделей для кодування.", "go.hero.title": "Недорогі моделі кодування для всіх", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index 4e10ae5d2003..ec6173e2a29a 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -248,7 +248,6 @@ export const dict = { "zen.privacy.exceptionsLink": "以下例外情况除外", "go.title": "OpenCode Go | 人人可用的低成本编程模型", - "go.banner.text": "GLM-5.3-Flash 限时享受 2 倍使用额度", "go.meta.description": "Go 每月 $10,提供充裕的使用限额,并可可靠访问领先的编程模型。", "go.hero.title": "人人可用的低成本编程模型", "go.hero.body": diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index 81a8cf7196f3..c088b268d8cf 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -247,7 +247,6 @@ export const dict = { "zen.privacy.exceptionsLink": "以下例外情況", "go.title": "OpenCode Go | 低成本全民編碼模型", - "go.banner.text": "GLM-5.3-Flash 限時享有 2 倍使用額度", "go.meta.description": "Go 每月 $10,提供充裕的使用限額,並可穩定存取領先的編碼模型。", "go.hero.title": "低成本全民編碼模型", "go.hero.body": diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index a0789ce65f96..59bbea8fcbfc 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -79,12 +79,6 @@ export default function Home() {
    -
    - {i18n.t("home.banner.badge")} -
    - {i18n.t("go.banner.text")} -
    -
    diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index a1551aa21e4d..bb085bd4a4f0 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -140,7 +140,7 @@ OpenCode Go هو اشتراك منخفض التكلفة بقيمة **$10/شهر | ---------------------------- | ------------------- | ------------------ | ---------------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -201,7 +201,7 @@ OpenCode Go هو اشتراك منخفض التكلفة بقيمة **$10/شهر | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index e81627d99470..58feac308c7b 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -150,7 +150,7 @@ Tabela ispod pruža procijenjeni broj zahtjeva na osnovu tipičnih obrazaca kori | ---------------------------- | ------------------ | ----------------- | ----------------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -211,7 +211,7 @@ Procjene se također zasnivaju na sljedećim cijenama po 1M tokena i mjesečnoj | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index bfe8da867e95..663140b9b1da 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -150,7 +150,7 @@ Tabellen nedenfor giver et estimeret antal anmodninger baseret på typiske Go-fo | ---------------------------- | ----------------------- | ------------------- | --------------------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -211,7 +211,7 @@ Estimaterne er også baseret på følgende priser pr. 1M tokens og det månedlig | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index dd37155b818d..cac9b3d6e94e 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -142,7 +142,7 @@ Die folgende Tabelle zeigt eine geschätzte Anzahl von Anfragen basierend auf ty | ---------------------------- | ---------------------- | ------------------ | ------------------ | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -203,7 +203,7 @@ Die Schätzungen basieren außerdem auf den folgenden Preisen pro 1M Tokens und | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index de8405114971..4f120adb5dea 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -150,7 +150,7 @@ La siguiente tabla proporciona una cantidad estimada de peticiones basada en los | ---------------------------- | ---------------------- | --------------------- | ------------------ | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -211,7 +211,7 @@ Las estimaciones también se basan en los siguientes precios por 1M tokens y en | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 97f3ecd00518..06c4452afc72 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -140,7 +140,7 @@ Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur d | ---------------------------- | --------------------- | -------------------- | ----------------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -201,7 +201,7 @@ Les estimations sont également basées sur les prix suivants par 1M tokens et s | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index ba4c06be2c48..ea3dfd047a5f 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -150,7 +150,7 @@ The table below provides an estimated request count based on typical Go usage pa | ---------------------------- | ------------------- | ----------------- | ------------------ | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -211,7 +211,7 @@ The estimates are also based on the following prices per 1M tokens and the month | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index 88491dc1b06f..581a46b244dc 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -148,7 +148,7 @@ La tabella seguente fornisce una stima del conteggio delle richieste in base a p | ---------------------------- | -------------------- | --------------------- | ----------------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -209,7 +209,7 @@ Le stime si basano anche sui seguenti prezzi per 1M token e sull'utilizzo mensil | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index edaaf32d413a..a7b402f0c7c7 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -137,7 +137,7 @@ OpenCode Goには以下の基本制限が含まれています: | ---------------------------- | ------------------------- | ---------------- | ---------------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -198,7 +198,7 @@ OpenCode Goには以下の基本制限が含まれています: | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index 76770bdfd488..5f35bef2c05d 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -137,7 +137,7 @@ OpenCode Go에는 다음과 같은 기본 한도가 포함됩니다. | ---------------------------- | ----------------- | -------------- | -------------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -198,7 +198,7 @@ OpenCode Go에는 다음과 같은 기본 한도가 포함됩니다. | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index 29d41be06170..44c019f31987 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -150,7 +150,7 @@ Tabellen nedenfor gir et estimert antall forespørsler basert på typiske bruksm | ---------------------------- | ------------------------ | -------------------- | ---------------------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -211,7 +211,7 @@ Estimatene er også basert på følgende priser per 1M tokens og den månedlige | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index bc89ca4eed96..3e8213b8aeec 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -144,7 +144,7 @@ Poniższa tabela przedstawia szacunkową liczbę żądań na podstawie typowych | ---------------------------- | ------------------- | ------------------ | ------------------ | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -205,7 +205,7 @@ Szacunki opierają się również na następujących cenach za 1M tokenów oraz | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index 140166856e3c..98cef1325a66 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -150,7 +150,7 @@ A tabela abaixo fornece uma contagem estimada de requisições com base nos padr | ---------------------------- | ----------------------- | ---------------------- | ------------------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -211,7 +211,7 @@ As estimativas também se baseiam nos seguintes preços por 1M tokens e no uso m | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index 22337f326a4e..0e8842d1484a 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -150,7 +150,7 @@ OpenCode Go включает следующие базовые лимиты: | ---------------------------- | ------------------- | ----------------- | ---------------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -211,7 +211,7 @@ OpenCode Go включает следующие базовые лимиты: | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index 29f5448a4d80..dfaa30d69e0b 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -137,7 +137,7 @@ OpenCode Go มีขีดจำกัดพื้นฐานดังต่ | ---------------------------- | ---------------------- | ------------------- | ----------------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -198,7 +198,7 @@ OpenCode Go มีขีดจำกัดพื้นฐานดังต่ | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index 764de377cfd3..1b6a41b45325 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -137,7 +137,7 @@ Aşağıdaki tablo, tipik Go kullanım modellerine dayalı tahmini bir istek say | ---------------------------- | ------------------ | -------------- | ----------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -198,7 +198,7 @@ Tahminler ayrıca 1M token başına aşağıdaki fiyatlara ve her modelle birlik | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index 60ac0cf1eae6..539fa1328743 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -136,7 +136,7 @@ OpenCode Go 包含以下基础限制: | ---------------------------- | --------------- | ---------- | ---------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -197,7 +197,7 @@ OpenCode Go 包含以下基础限制: | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index e81efc392cdf..cd5e690af914 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -136,7 +136,7 @@ OpenCode Go 包含以下基準限制: | ---------------------------- | --------------- | ---------- | ---------- | | Grok 4.6 | 169 | 423 | 845 | | GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 1,580 | 3,950 | 7,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | | GLM-5.1 | 880 | 2,150 | 4,300 | @@ -197,7 +197,7 @@ OpenCode Go 包含以下基準限制: | Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | | GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | | GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $15 | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | | GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | | GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | From 9f8db119fcbd4999379129ac7734375ac23460fb Mon Sep 17 00:00:00 2001 From: Victor Navarro Date: Wed, 9 Sep 2026 19:45:38 +0200 Subject: [PATCH 058/129] feat(console): route migrated Go inference (#48123) --- .../console/app/src/lib/inference-proxy.ts | 35 ++++++++++++++++--- .../app/src/routes/zen/go/v1/models.ts | 5 ++- .../console/app/src/routes/zen/go/v1/usage.ts | 3 ++ .../app/src/routes/zen/util/handler.ts | 16 ++++----- .../console/app/src/routes/zen/v1/models.ts | 32 ++--------------- 5 files changed, 47 insertions(+), 44 deletions(-) diff --git a/packages/console/app/src/lib/inference-proxy.ts b/packages/console/app/src/lib/inference-proxy.ts index 7de73a961ac8..b572c361b655 100644 --- a/packages/console/app/src/lib/inference-proxy.ts +++ b/packages/console/app/src/lib/inference-proxy.ts @@ -8,11 +8,17 @@ const paths: Record = { "POST /zen/v1/chat/completions": "/openai/v1/chat/completions", "POST /zen/v1/responses": "/openai/v1/responses", "POST /zen/v1/messages": "/anthropic/v1/messages", + "POST /zen/go/v1/chat/completions": "/go/openai/v1/chat/completions", + "POST /zen/go/v1/responses": "/go/openai/v1/responses", + "POST /zen/go/v1/messages": "/go/anthropic/v1/messages", + "GET /zen/v1/models": "/v1/models", + "GET /zen/go/v1/models": "/go/v1/models", + "GET /zen/go/v1/usage": "/go/v1/usage", } export async function proxyInference( request: Request, - generation: { + generation?: { provider?: "openai" | "anthropic" | "google" /** The provider's native model ID, not the public Zen alias. */ model?: string @@ -28,7 +34,8 @@ export async function proxyInference( : undefined) if (!path) return undefined - const key = path.startsWith("/anthropic/") + const go = url.pathname.startsWith("/zen/go/") + const key = url.pathname.endsWith("/messages") ? request.headers.get("x-api-key") : path.startsWith("/google/") ? request.headers.get("x-goog-api-key") @@ -47,7 +54,7 @@ export async function proxyInference( .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID)) .leftJoin( ProviderTable, - generation.provider + !go && generation?.provider ? and( eq(ProviderTable.workspaceID, KeyTable.workspaceID), eq(ProviderTable.provider, generation.provider), @@ -61,7 +68,7 @@ export async function proxyInference( .then((rows) => rows[0]), ) if (!workspace?.migratedAt) return undefined - const model = workspace.provider ? generation.model : undefined + const model = workspace.provider ? generation?.model : undefined if (workspace.provider && !model) throw new Error("Legacy BYOK model mapping is unavailable") const destination = new URL(Resource.ConsoleMigration.inferenceUrl) @@ -80,8 +87,19 @@ export async function proxyInference( // Model extraction has already read part of the body; forward its replay stream. const forwarded = new Request( destination, - new Request(request, { method: request.method, body: generation.body(model) }), + generation ? new Request(request, { method: request.method, body: generation.body(model) }) : request, ) + // Migrated requests use ordinary destination authentication and accounting. + for (const name of [ + "x-zen", + "x-zen-model", + "x-zen-ip", + "cf-access-client-id", + "cf-access-client-secret", + "host", + "content-length", + ]) + forwarded.headers.delete(name) forwarded.headers.set("authorization", `Bearer ${key}`) const ip = request.headers.get("cf-connecting-ip") if (ip) forwarded.headers.set("x-real-ip", ip) @@ -90,3 +108,10 @@ export async function proxyInference( return fetch(forwarded, { redirect: "manual" }) } + +export function inferenceUnavailable() { + return Response.json( + { error: { type: "api_error", message: "Inference routing is unavailable. Please retry later." } }, + { status: 503, headers: { "Cache-Control": "no-store" } }, + ) +} diff --git a/packages/console/app/src/routes/zen/go/v1/models.ts b/packages/console/app/src/routes/zen/go/v1/models.ts index 83ef74fb6148..dfc7b87ec34a 100644 --- a/packages/console/app/src/routes/zen/go/v1/models.ts +++ b/packages/console/app/src/routes/zen/go/v1/models.ts @@ -1,12 +1,15 @@ import type { APIEvent } from "@solidjs/start/server" import { ZenData } from "@opencode-ai/console-core/model.js" import { buildModelsResponse, buildOptionsResponse } from "../../util/modelsHandler" +import { inferenceUnavailable, proxyInference } from "~/lib/inference-proxy" export async function OPTIONS(_input: APIEvent) { return buildOptionsResponse() } -export async function GET(_input: APIEvent) { +export async function GET(input: APIEvent) { + const response = await proxyInference(input.request).catch(inferenceUnavailable) + if (response) return response const models = Object.keys(ZenData.list("lite").models) return buildModelsResponse(models) } diff --git a/packages/console/app/src/routes/zen/go/v1/usage.ts b/packages/console/app/src/routes/zen/go/v1/usage.ts index c16851dc78ff..a41373f1aae6 100644 --- a/packages/console/app/src/routes/zen/go/v1/usage.ts +++ b/packages/console/app/src/routes/zen/go/v1/usage.ts @@ -6,8 +6,11 @@ import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js" import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js" import { LiteData } from "@opencode-ai/console-core/lite.js" import { Subscription } from "@opencode-ai/console-core/subscription.js" +import { inferenceUnavailable, proxyInference } from "~/lib/inference-proxy" export async function GET(input: APIEvent) { + const response = await proxyInference(input.request).catch(inferenceUnavailable) + if (response) return response const apiKey = input.request.headers.get("authorization")?.match(/^Bearer (\S+)$/)?.[1] if (!apiKey) { diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index d5e2fd811ea2..ee223498cae9 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -50,7 +50,7 @@ import { countryFromRequest, isModelCountryRestricted } from "~/lib/request-coun import { isPeakPricing } from "./pricing" import { prepareRequestBody } from "./requestBody" import { requiresGoTrainingConsent } from "./trainingConsent" -import { proxyInference } from "~/lib/inference-proxy" +import { inferenceUnavailable, proxyInference } from "~/lib/inference-proxy" type ZenData = Awaited> type PreparedBody = Awaited> @@ -102,22 +102,22 @@ export async function handler( const rawZenApiKey = opts.parseApiKey(input.request.headers) const zenApiKey = rawZenApiKey === "public" ? undefined : rawZenApiKey const zenData = ZenData.list(opts.modelList) - if (opts.modelList === "full" && model) { + if (model) { // Read routing metadata without running legacy model, auth, or balance checks. const configured = zenData.models[model] const entry = Array.isArray(configured) ? configured.find((entry) => entry.formatFilter === opts.format) : configured const response = await proxyInference(input.request, { - provider: entry?.byokProvider, - model: entry?.providers.find((provider) => provider.id === entry.byokProvider)?.model, + provider: opts.modelList === "full" ? entry?.byokProvider : undefined, + model: + opts.modelList === "full" + ? entry?.providers.find((provider) => provider.id === entry.byokProvider)?.model + : undefined, body: (providerModel) => requestBody?.stream(providerModel ?? model, false) ?? body, }).catch(() => { void (requestBody ? requestBody.cancel() : body.cancel()).catch(() => {}) - return Response.json( - { error: { type: "api_error", message: "Inference routing is unavailable. Please retry later." } }, - { status: 503, headers: { "Cache-Control": "no-store" } }, - ) + return inferenceUnavailable() }) if (response) return response } diff --git a/packages/console/app/src/routes/zen/v1/models.ts b/packages/console/app/src/routes/zen/v1/models.ts index 262a1bb349fe..c1505b899cf3 100644 --- a/packages/console/app/src/routes/zen/v1/models.ts +++ b/packages/console/app/src/routes/zen/v1/models.ts @@ -5,7 +5,7 @@ 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 { buildOptionsResponse, buildModelsResponse } from "~/routes/zen/util/modelsHandler" -import { Resource } from "@opencode-ai/console-resource" +import { inferenceUnavailable, proxyInference } from "~/lib/inference-proxy" export async function OPTIONS(_input: APIEvent) { return buildOptionsResponse() @@ -14,12 +14,7 @@ export async function OPTIONS(_input: APIEvent) { export async function GET(input: APIEvent) { const apiKey = input.request.headers.get("authorization")?.split(" ")[1] if (apiKey && apiKey !== "public") { - const response = await proxyModels(input, apiKey).catch(() => - Response.json( - { error: { type: "api_error", message: "Inference routing is unavailable. Please retry later." } }, - { status: 503, headers: { "Cache-Control": "no-store" } }, - ), - ) + const response = await proxyInference(input.request).catch(inferenceUnavailable) if (response) return response } @@ -45,26 +40,3 @@ export async function GET(input: APIEvent) { return buildModelsResponse(models) } - -async function proxyModels(input: APIEvent, apiKey: string) { - // No legacy revocation or model-policy checks before destination authentication. - const workspace = await Database.use((tx) => - tx - .select({ migratedAt: WorkspaceTable.migrated_at }) - .from(KeyTable) - .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID)) - .where(eq(KeyTable.key, apiKey)) - .limit(1) - .then((rows) => rows[0]), - ) - if (!workspace?.migratedAt) return undefined - - const destination = new URL(Resource.ConsoleMigration.inferenceUrl) - destination.pathname = `${destination.pathname.replace(/\/$/, "")}/v1/models` - destination.search = new URL(input.request.url).search - destination.hash = "" - const headers = new Headers({ authorization: `Bearer ${apiKey}` }) - const ip = input.request.headers.get("cf-connecting-ip") - if (ip) headers.set("x-real-ip", ip) - return fetch(destination, { headers, signal: input.request.signal, redirect: "manual" }) -} From 72635a37209200041b659f0db6d07e3188dea444 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 10 Sep 2026 06:44:00 +0800 Subject: [PATCH 059/129] feat(console): clarify Go model usage (#48192) Co-authored-by: Frank --- .../console/app/src/component/go-models.ts | 43 +++ .../app/src/component/limits-graph.css | 293 +++++++++++++++++ .../app/src/component/limits-graph.tsx | 297 +++++++----------- packages/console/app/src/i18n/ar.ts | 8 + packages/console/app/src/i18n/br.ts | 8 + packages/console/app/src/i18n/da.ts | 10 +- 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 | 10 +- 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/uk.ts | 8 + packages/console/app/src/i18n/zh.ts | 8 + packages/console/app/src/i18n/zht.ts | 8 + packages/console/app/src/lib/language.ts | 28 ++ packages/console/app/src/routes/go/index.tsx | 3 +- packages/web/src/content/docs/ar/go.mdx | 116 +++---- packages/web/src/content/docs/bs/go.mdx | 110 +++---- packages/web/src/content/docs/da/go.mdx | 166 +++++----- packages/web/src/content/docs/de/go.mdx | 166 +++++----- packages/web/src/content/docs/es/go.mdx | 110 +++---- packages/web/src/content/docs/fr/go.mdx | 166 +++++----- packages/web/src/content/docs/go.mdx | 112 +++---- packages/web/src/content/docs/it/go.mdx | 166 +++++----- packages/web/src/content/docs/ja/go.mdx | 166 +++++----- packages/web/src/content/docs/ko/go.mdx | 164 +++++----- packages/web/src/content/docs/nb/go.mdx | 160 +++++----- packages/web/src/content/docs/pl/go.mdx | 164 +++++----- packages/web/src/content/docs/pt-br/go.mdx | 116 +++---- packages/web/src/content/docs/ru/go.mdx | 166 +++++----- packages/web/src/content/docs/th/go.mdx | 164 +++++----- packages/web/src/content/docs/tr/go.mdx | 166 +++++----- packages/web/src/content/docs/zh-cn/go.mdx | 116 +++---- packages/web/src/content/docs/zh-tw/go.mdx | 118 +++---- 41 files changed, 1959 insertions(+), 1465 deletions(-) create mode 100644 packages/console/app/src/component/go-models.ts create mode 100644 packages/console/app/src/component/limits-graph.css diff --git a/packages/console/app/src/component/go-models.ts b/packages/console/app/src/component/go-models.ts new file mode 100644 index 000000000000..0aa2f1704b99 --- /dev/null +++ b/packages/console/app/src/component/go-models.ts @@ -0,0 +1,43 @@ +// Requests are per 5 hours; allowances are monthly usage at official API prices. +export const goModels = [ + { id: "kimi-k3", name: "Kimi K3", requests: 110, allowance: 15, featured: true }, + { id: "qwen3.8-max", name: "Qwen3.8 Max", requests: 160, allowance: 15 }, + { id: "grok-4.6", name: "Grok 4.6", requests: 169, allowance: 15 }, + { id: "qwen3.7-max", name: "Qwen3.7 Max", requests: 170, allowance: 30 }, + { id: "glm-5.3", name: "GLM-5.3", requests: 220, allowance: 15 }, + { id: "glm-5.2", name: "GLM-5.2", requests: 880, allowance: 60 }, + { id: "glm-5.1", name: "GLM-5.1", requests: 880, allowance: 60 }, + { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", requests: 1050, allowance: 15 }, + { id: "kimi-k2.6", name: "Kimi K2.6", requests: 1150, allowance: 60 }, + { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", requests: 1350, allowance: 60, featured: true }, + { id: "hy4-preview", name: "Hy4 preview", requests: 1350, allowance: 30 }, + { id: "gpt-5.6-luna", name: "GPT 5.6 Luna", requests: 2050, allowance: 15, featured: true }, + { id: "minimax-m3", name: "MiniMax M3", requests: 3200, allowance: 60, featured: true }, + { id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", requests: 3250, allowance: 15 }, + { id: "qwen3.6-plus", name: "Qwen3.6 Plus", requests: 3300, allowance: 60 }, + { id: "minimax-m2.7", name: "MiniMax M2.7", requests: 3400, allowance: 60 }, + { id: "deepseek-v4-flash-vision-exp", name: "DeepSeek V4 Flash Vision Exp", requests: 3800, allowance: 15 }, + { id: "qwen3.7-plus", name: "Qwen3.7 Plus", requests: 4300, allowance: 60, featured: true }, + { id: "hy3", name: "Hy3", requests: 4300, allowance: 60 }, + { id: "qwen3.8-flash", name: "Qwen3.8 Flash", requests: 5400, allowance: 30 }, + { id: "glm-5.3-flash", name: "GLM-5.3-Flash", requests: 6320, allowance: 60, featured: true }, + { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", requests: 7600, allowance: 30, featured: true }, + { id: "longcat-2.0", name: "LongCat-2.0", requests: 11400, allowance: 60 }, + { id: "omen-alpha", name: "Omen Alpha", requests: 11600, allowance: 100, featured: true, fresh: true }, + { id: "mimo-v2.5", name: "MiMo-V2.5", requests: 30100, allowance: 60, featured: true }, + { + id: "muse-spark-1.3-contributor", + name: "Muse Spark 1.3 Contributor", + requests: 45300, + allowance: 60, + featured: true, + regions: true, + }, + { + id: "muse-spark-1.2-contributor", + name: "Muse Spark 1.2 Contributor", + requests: 45300, + allowance: 60, + regions: true, + }, +].sort((a, b) => a.requests - b.requests) diff --git a/packages/console/app/src/component/limits-graph.css b/packages/console/app/src/component/limits-graph.css new file mode 100644 index 000000000000..c03996c96e6c --- /dev/null +++ b/packages/console/app/src/component/limits-graph.css @@ -0,0 +1,293 @@ +[data-component="go-usage"] { + --model-width: 16.5rem; + --request-width: 5.25rem; + --allowance-width: 6.75rem; + --column-gap: 1.5rem; + --bar-origin: left; + container-type: inline-size; + margin: 0; + padding: 3rem var(--padding) 2.5rem; + font-variant-numeric: tabular-nums; + scroll-margin-top: 6rem; + -webkit-font-smoothing: antialiased; + + &:dir(rtl) { + --bar-origin: right; + } + + [data-slot="heading"] { + padding-bottom: 1.5rem; + + h2 { + color: var(--color-text-strong); + font-size: 1rem; + font-weight: 600; + } + } + + [data-slot="columns"], + [data-slot="model-row"] { + display: grid; + grid-template-columns: var(--model-width) minmax(0, 1fr) var(--allowance-width); + align-items: center; + column-gap: var(--column-gap); + } + + [data-slot="columns"] { + padding-bottom: 0.875rem; + border-bottom: 1px solid var(--color-border-weak); + font-size: 0.6875rem; + color: var(--color-text); + overflow-wrap: anywhere; + } + + [data-slot="requests-heading"], + [data-slot="allowance-heading"] { + text-align: end; + } + + [data-slot="rows"] { + padding-top: 0.375rem; + } + + [data-slot="model-row"] { + min-height: 2.5rem; + font-size: 0.8125rem; + } + + [data-slot="model"] { + display: flex; + align-items: baseline; + flex-wrap: wrap; + gap: 0.375rem 0.5rem; + min-width: 0; + line-height: 1.5; + color: var(--color-text); + font-weight: 400; + } + + [data-slot="badge"] { + color: var(--color-text-strong); + font-size: 0.625rem; + line-height: 1.3; + border: 1px solid var(--color-border); + padding: 0.0625rem 0.25rem; + } + + [data-slot="region"] { + display: inline-flex; + align-self: center; + padding: 0.25rem; + color: var(--color-text); + text-decoration: none; + + &:hover { + color: var(--color-text-strong); + } + } + + [data-slot="usage-value"] { + display: grid; + grid-template-columns: minmax(0, 1fr) var(--request-width); + align-items: center; + gap: var(--column-gap); + align-self: stretch; + } + + [data-slot="track"] { + display: flex; + align-items: center; + align-self: stretch; + position: relative; + isolation: isolate; + } + + [data-slot="gridline"] { + position: absolute; + inset-block: 0; + inset-inline-start: var(--position); + border-inline-start: 1px dotted var(--color-border-weak); + z-index: -1; + } + + [data-slot="bar"] { + width: var(--width); + height: 0.375rem; + background: var(--color-go-2); + transform-origin: var(--bar-origin); + } + + &[data-visible] [data-slot="bar"] { + animation: go-usage-reveal 700ms cubic-bezier(0.22, 1, 0.36, 1) both; + animation-delay: var(--delay); + } + + [data-slot="requests"] { + text-align: end; + font-weight: 600; + color: var(--color-text-strong); + } + + [data-slot="allowance"] { + text-align: end; + color: var(--color-text); + + &[data-high] { + color: var(--color-text-strong); + } + } + + [data-slot="currency"] { + color: var(--color-text-weak); + } + + [data-slot="axis"] { + padding-inline-start: calc(var(--model-width) + var(--column-gap)); + padding-inline-end: calc(var(--request-width) + var(--allowance-width) + 2 * var(--column-gap)); + height: 2.5rem; + } + + [data-slot="ticks"] { + position: relative; + height: 100%; + font-size: 0.625rem; + color: var(--color-text-weak); + + span { + position: absolute; + inset-inline-start: var(--position); + top: 0.75rem; + transform: translateX(-50%); + + &:dir(rtl) { + transform: translateX(50%); + } + } + } + + [data-slot="expand"] { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; + min-height: 2.75rem; + padding: 0.75rem 0; + background: none; + border: none; + color: var(--color-text); + font-family: inherit; + font-size: 0.75rem; + cursor: pointer; + + &:hover { + color: var(--color-text-strong); + } + &[aria-expanded="true"] svg { + transform: rotate(180deg); + } + } + + :is(button, a):focus-visible { + outline: 1px solid var(--color-text-strong); + outline-offset: 4px; + } + + figcaption { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + gap: 1rem; + border-top: 1px solid var(--color-border-weak); + font-size: 0.75rem; + color: var(--color-text); + + a { + color: inherit; + white-space: nowrap; + } + } + + @container (max-width: 48rem) { + [data-slot="columns"], + [data-slot="model-row"], + [data-slot="axis"] { + --model-width: 13rem; + --request-width: 4rem; + --allowance-width: 6rem; + --column-gap: 1rem; + } + } + + @container (max-width: 38rem) { + [data-slot="columns"], + [data-slot="model-row"] { + --request-width: 5rem; + --allowance-width: 4.5rem; + --column-gap: 0.75rem; + grid-template-columns: minmax(0, 1fr) var(--request-width) var(--allowance-width); + } + + [data-slot="columns"] { + font-size: 0.625rem; + } + + [data-slot="model-row"] { + min-height: 4rem; + padding-block: 0.375rem; + row-gap: 0.25rem; + font-size: 1rem; + } + + [data-slot="model"] { + grid-column: 1 / -1; + } + [data-slot="usage-value"] { + display: contents; + } + + [data-slot="track"] { + grid-column: 1; + grid-row: 2; + align-self: center; + height: 4px; + } + + [data-slot="bar"] { + height: 4px; + } + [data-slot="requests"] { + grid-column: 2; + grid-row: 2; + } + [data-slot="allowance"] { + grid-column: 3; + grid-row: 2; + } + [data-slot="gridline"], + [data-slot="axis"] { + display: none; + } + [data-slot="expand"] { + font-size: 0.8125rem; + } + figcaption { + margin-top: 1rem; + } + } + + @media (prefers-reduced-motion: reduce) { + &[data-visible] [data-slot="bar"] { + animation: none; + } + } +} + +@keyframes go-usage-reveal { + from { + transform: scaleX(0); + } + to { + transform: scaleX(1); + } +} diff --git a/packages/console/app/src/component/limits-graph.tsx b/packages/console/app/src/component/limits-graph.tsx index aa9744ecbb8a..b10de2969685 100644 --- a/packages/console/app/src/component/limits-graph.tsx +++ b/packages/console/app/src/component/limits-graph.tsx @@ -1,209 +1,156 @@ -import { For, createSignal, onCleanup, onMount } from "solid-js" +import "./limits-graph.css" +import { For, Show, createMemo, createSignal, createUniqueId, onCleanup, onMount } from "solid-js" import { useI18n } from "~/context/i18n" +import { useLanguage } from "~/context/language" +import { goModels } from "./go-models" -export function LimitsGraph(props: { href: string }) { - let root!: HTMLElement - const [visible, setVisible] = createSignal(false) +// Compress the request range, with the same domain in both views. +const max = Math.max(...goModels.map((model) => model.requests)) +const position = (requests: number) => + 4 + Math.pow(Math.log10(Math.max(requests / 100, 1)) / Math.log10(max / 100), 2.2) * 96 +const ticks = [100, 1000, 10000, 40000] +export function LimitsGraph(props: { href: string }) { const i18n = useI18n() + const language = useLanguage() + const id = createUniqueId() + const [expanded, setExpanded] = createSignal(false) + const [visible, setVisible] = createSignal(false) + const models = createMemo(() => goModels.filter((model) => expanded() || model.featured || model.fresh)) + const format = createMemo(() => new Intl.NumberFormat(language.tag(language.locale()))) + const compact = createMemo( + () => new Intl.NumberFormat(language.tag(language.locale()), { notation: "compact", maximumFractionDigits: 1 }), + ) + const currency = createMemo( + () => + new Intl.NumberFormat(language.tag(language.locale()), { + style: "currency", + currency: "USD", + currencyDisplay: "narrowSymbol", + maximumFractionDigits: 0, + }), + ) + let root!: HTMLElement onMount(() => { - const motion = window.matchMedia("(prefers-reduced-motion: reduce)") - const finish = () => { - if (!motion.matches) return - setVisible(true) - } - motion.addEventListener("change", finish) - onCleanup(() => { - motion.removeEventListener("change", finish) - }) - if (motion.matches) return finish() - if (typeof IntersectionObserver === "undefined") return setVisible(true) + if (!window.IntersectionObserver) return setVisible(true) const observer = new IntersectionObserver( (entries) => { - const entry = entries[0] - if (!entry?.isIntersecting || entry.intersectionRatio < 0.35) return + if (!entries.some((entry) => entry.isIntersecting)) return setVisible(true) observer.disconnect() }, - { threshold: 0.35 }, + { threshold: 0.1 }, ) observer.observe(root) onCleanup(() => observer.disconnect()) }) - const baseline = 100 - const graph = [ - { id: "kimi-k3", name: "Kimi K3", req: 110 }, - { id: "grok-4.6", name: "Grok 4.6", req: 169 }, - { id: "hy4-preview", name: "Hy4 preview", req: 1350 }, - { id: "gpt-5.6-luna", name: "GPT 5.6 Luna", req: 2050 }, - { id: "minimax-m3", name: "MiniMax M3", req: 3200 }, - { id: "qwen3.7-plus", name: "Qwen3.7 Plus", req: 4300 }, - { id: "hy3", name: "Hy3", req: 4300 }, - { id: "qwen3.8-flash", name: "Qwen3.8 Flash", req: 5400 }, - { id: "glm-5.3-flash", name: "GLM-5.3-Flash", req: 6320 }, - { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 7600 }, - { id: "longcat-2.0", name: "LongCat-2.0", req: 11400 }, - { id: "omen-alpha", name: "Omen Alpha", req: 11600 }, - { id: "mimo-v2.5", name: "MiMo-V2.5", req: 30100 }, - { id: "muse-spark-1.3-contributor", name: "Muse Spark 1.3 Contributor", req: 45300, edge: true }, - ].map((model, index) => ({ ...model, d: `${50 + index * 25}ms` })) - - const w = 1040 - const chartW = 720 - const left = 40 - const right = 60 - const top = 18 - const bottom = 44 - const plot = chartW - left - right - const infiniteX = w - 180 - - const ratio = (n: number) => n / baseline - const rmax = Math.max(1, ...graph.filter((m) => !("infinite" in m)).map((m) => ratio(m.req))) - const log = (n: number) => Math.log10(Math.max(n, 1)) - const base = 24 - const p = 2.2 - const x = (r: number) => left + base + Math.pow(log(r) / log(rmax), p) * (plot - base) - const ticks = [1, 5, 10, 25, 50, 100, 250].filter((t) => t <= rmax) - const labels = (() => { - const set = new Set() - let last = -Infinity - for (const t of ticks) { - if (t === 1) { - set.add(t) - last = x(t) - continue - } - const pos = x(t) - if (pos - last < 44) continue - set.add(t) - last = pos - } - return set - })() - const shown = ticks.filter((t) => labels.has(t)) - const bh = 8 - const gap = 20 - const step = bh + gap - const gy = (i: number) => top + 22 + step * i - const h = gy(graph.length - 1) + bottom - const my = graph.length < 2 ? gy(0) : (gy(0) + gy(graph.length - 1)) / 2 - const px = (n: number) => `${(n / w) * 100}%` - const py = (n: number) => `${(n / h) * 100}%` - const lx = px(left - 16) - const ty = py(h - 18) - return (
    -
    - +
    +

    {i18n.t("go.graph.period")}

    +
    -
    ) diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index 2d792158741f..035794a23bb1 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -270,6 +270,14 @@ export const dict = { "go.graph.freePill": "Big Pickle ونماذج مجانية", "go.graph.go": "Go", "go.graph.label": "الطلبات كل 5 ساعات", + "go.graph.period": "الاستخدام", + "go.graph.model": "النموذج", + "go.graph.requests": "الطلبات المقدّرة / ٥ ساعات", + "go.graph.allowance": "الاستخدام الشهري", + "go.graph.new": "جديد", + "go.graph.scale": "مقياس غير خطي لعدد الطلبات", + "go.graph.showAll": "عرض جميع النماذج ({{count}})", + "go.graph.showLess": "عرض نماذج أقل", "go.graph.limitedRegions": "مناطق محدودة", "go.graph.limitedTime": "لفترة محدودة", "go.graph.usageLimits": "حدود الاستخدام", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index ded260326811..9afa3e27d8af 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -276,6 +276,14 @@ export const dict = { "go.graph.freePill": "Big Pickle e modelos gratuitos", "go.graph.go": "Go", "go.graph.label": "Requisições por 5 horas", + "go.graph.period": "Uso", + "go.graph.model": "Modelo", + "go.graph.requests": "Requisições estimadas / 5 h", + "go.graph.allowance": "Uso mensal", + "go.graph.new": "Novo", + "go.graph.scale": "Escala não linear de requisições", + "go.graph.showAll": "Ver todos os {{count}} modelos", + "go.graph.showLess": "Mostrar menos modelos", "go.graph.limitedRegions": "regiões limitadas", "go.graph.limitedTime": "tempo limitado", "go.graph.usageLimits": "Limites de uso", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index de296cb0f555..b7b39f7b0e8d 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -273,9 +273,17 @@ export const dict = { "go.graph.freePill": "Big Pickle og gratis modeller", "go.graph.go": "Go", "go.graph.label": "Forespørgsler pr. 5 timer", + "go.graph.period": "Forbrug", + "go.graph.model": "Model", + "go.graph.requests": "Estimerede anmodninger / 5 timer", + "go.graph.allowance": "Månedligt forbrug", + "go.graph.new": "Ny", + "go.graph.scale": "Ikke-lineær skala for anmodninger", + "go.graph.showAll": "Vis alle {{count}} modeller", + "go.graph.showLess": "Vis færre modeller", "go.graph.limitedRegions": "begrænsede regioner", "go.graph.limitedTime": "begrænset periode", - "go.graph.usageLimits": "Brugsgrænser", + "go.graph.usageLimits": "Forbrugsgrænser", "go.graph.aria": "Forespørgsler pr. 5t: {{free}} vs {{go}}", "go.testimonials.brand.zen": "Zen", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index 898a9d87a33a..9f31cd89ec34 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -275,6 +275,14 @@ export const dict = { "go.graph.freePill": "Big Pickle und kostenlose Modelle", "go.graph.go": "Go", "go.graph.label": "Anfragen pro 5 Stunden", + "go.graph.period": "Nutzung", + "go.graph.model": "Modell", + "go.graph.requests": "Geschätzte Anfragen / 5 Std.", + "go.graph.allowance": "Monatliche Nutzung", + "go.graph.new": "Neu", + "go.graph.scale": "Nichtlineare Skala für Anfragen", + "go.graph.showAll": "Alle {{count}} Modelle anzeigen", + "go.graph.showLess": "Weniger Modelle anzeigen", "go.graph.limitedRegions": "begrenzte Regionen", "go.graph.limitedTime": "begrenzte Zeit", "go.graph.usageLimits": "Nutzungslimits", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index 1031a72e706a..44cd2d74eb56 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -270,6 +270,14 @@ export const dict = { "go.graph.freePill": "Big Pickle and free models", "go.graph.go": "Go", "go.graph.label": "Requests / 5 hours", + "go.graph.period": "Usage", + "go.graph.model": "Model", + "go.graph.requests": "Est. requests / 5 hr", + "go.graph.allowance": "Monthly usage", + "go.graph.new": "New", + "go.graph.scale": "Nonlinear request scale", + "go.graph.showAll": "View all {{count}} models", + "go.graph.showLess": "Show fewer models", "go.graph.limitedRegions": "limited regions", "go.graph.limitedTime": "limited time", "go.graph.tick": "{{n}}x", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 4e3d5cd41cb1..89634e4f9c84 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -277,6 +277,14 @@ export const dict = { "go.graph.freePill": "Big Pickle y modelos gratuitos", "go.graph.go": "Go", "go.graph.label": "Solicitudes por 5 horas", + "go.graph.period": "Uso", + "go.graph.model": "Modelo", + "go.graph.requests": "Peticiones estimadas / 5 h", + "go.graph.allowance": "Uso mensual", + "go.graph.new": "Nuevo", + "go.graph.scale": "Escala no lineal de peticiones", + "go.graph.showAll": "Ver los {{count}} modelos", + "go.graph.showLess": "Mostrar menos modelos", "go.graph.limitedRegions": "regiones limitadas", "go.graph.limitedTime": "tiempo limitado", "go.graph.usageLimits": "Límites de uso", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index fa1de59ee967..5c0c3e95951a 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -278,9 +278,17 @@ export const dict = { "go.graph.freePill": "Big Pickle et modèles gratuits", "go.graph.go": "Go", "go.graph.label": "Requêtes par tranche de 5 heures", + "go.graph.period": "Utilisation", + "go.graph.model": "Modèle", + "go.graph.requests": "Requêtes estimées / 5 h", + "go.graph.allowance": "Utilisation mensuelle", + "go.graph.new": "Nouveau", + "go.graph.scale": "Échelle non linéaire des requêtes", + "go.graph.showAll": "Voir les {{count}} modèles", + "go.graph.showLess": "Afficher moins de modèles", "go.graph.limitedRegions": "régions limitées", "go.graph.limitedTime": "durée limitée", - "go.graph.usageLimits": "Limites d'utilisation", + "go.graph.usageLimits": "Limites d’utilisation", "go.graph.aria": "Requêtes par 5h : {{free}} vs {{go}}", "go.testimonials.brand.zen": "Zen", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 0c81b6e94e14..82d32ab25e4f 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -274,6 +274,14 @@ export const dict = { "go.graph.freePill": "Big Pickle e modelli gratuiti", "go.graph.go": "Go", "go.graph.label": "Richieste ogni 5 ore", + "go.graph.period": "Utilizzo", + "go.graph.model": "Modello", + "go.graph.requests": "Richieste stimate / 5 ore", + "go.graph.allowance": "Utilizzo mensile", + "go.graph.new": "Nuovo", + "go.graph.scale": "Scala non lineare delle richieste", + "go.graph.showAll": "Mostra tutti i {{count}} modelli", + "go.graph.showLess": "Mostra meno modelli", "go.graph.limitedRegions": "regioni limitate", "go.graph.limitedTime": "periodo limitato", "go.graph.usageLimits": "Limiti di utilizzo", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index 534f8a95e948..ba0491b97426 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -273,6 +273,14 @@ export const dict = { "go.graph.freePill": "Big Pickleと無料モデル", "go.graph.go": "Go", "go.graph.label": "5時間あたりのリクエスト数", + "go.graph.period": "利用枠", + "go.graph.model": "モデル", + "go.graph.requests": "推定リクエスト数 / 5時間", + "go.graph.allowance": "月間利用枠", + "go.graph.new": "新着", + "go.graph.scale": "リクエスト数は非線形目盛りで表示", + "go.graph.showAll": "全{{count}}モデルを表示", + "go.graph.showLess": "折りたたむ", "go.graph.limitedRegions": "一部の地域に限定", "go.graph.limitedTime": "期間限定", "go.graph.usageLimits": "利用制限", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index f480675c9f48..c0fe7f887379 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -269,6 +269,14 @@ export const dict = { "go.graph.freePill": "Big Pickle 및 무료 모델", "go.graph.go": "Go", "go.graph.label": "5시간당 요청 수", + "go.graph.period": "사용량", + "go.graph.model": "모델", + "go.graph.requests": "예상 요청 횟수 / 5시간", + "go.graph.allowance": "월간 사용량", + "go.graph.new": "신규", + "go.graph.scale": "요청 수는 비선형 눈금으로 표시됩니다", + "go.graph.showAll": "전체 {{count}}개 모델 보기", + "go.graph.showLess": "접기", "go.graph.limitedRegions": "일부 지역에서만 제공", "go.graph.limitedTime": "한정된 기간", "go.graph.usageLimits": "사용 한도", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index 95fd5f5e9c42..801eed55740e 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -273,6 +273,14 @@ export const dict = { "go.graph.freePill": "Big Pickle og gratis modeller", "go.graph.go": "Go", "go.graph.label": "Forespørsler per 5 timer", + "go.graph.period": "Bruk", + "go.graph.model": "Modell", + "go.graph.requests": "Estimerte forespørsler / 5 timer", + "go.graph.allowance": "Månedlig bruk", + "go.graph.new": "Ny", + "go.graph.scale": "Ikke-lineær skala for forespørsler", + "go.graph.showAll": "Vis alle {{count}} modeller", + "go.graph.showLess": "Vis færre modeller", "go.graph.limitedRegions": "begrensede regioner", "go.graph.limitedTime": "begrenset periode", "go.graph.usageLimits": "Bruksgrenser", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index b27528fc23cf..8716919d5c92 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -275,6 +275,14 @@ export const dict = { "go.graph.freePill": "Big Pickle i darmowe modele", "go.graph.go": "Go", "go.graph.label": "Żądania na 5 godzin", + "go.graph.period": "Użycie", + "go.graph.model": "Model", + "go.graph.requests": "Szac. żądania / 5 godz.", + "go.graph.allowance": "Miesięczne użycie", + "go.graph.new": "Nowość", + "go.graph.scale": "Nieliniowa skala liczby żądań", + "go.graph.showAll": "Pokaż wszystkie modele ({{count}})", + "go.graph.showLess": "Pokaż mniej modeli", "go.graph.limitedRegions": "ograniczone regiony", "go.graph.limitedTime": "ograniczony czas", "go.graph.usageLimits": "Limity użycia", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index d2154ef83012..df3f4fbc99c1 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -278,6 +278,14 @@ export const dict = { "go.graph.freePill": "Big Pickle и бесплатные модели", "go.graph.go": "Go", "go.graph.label": "Запросов за 5 часов", + "go.graph.period": "Использование", + "go.graph.model": "Модель", + "go.graph.requests": "Примерное число запросов / 5 ч", + "go.graph.allowance": "Использование за месяц", + "go.graph.new": "Новое", + "go.graph.scale": "Нелинейная шкала числа запросов", + "go.graph.showAll": "Показать все модели ({{count}})", + "go.graph.showLess": "Показать меньше моделей", "go.graph.limitedRegions": "доступно в отдельных регионах", "go.graph.limitedTime": "ограниченное время", "go.graph.usageLimits": "Лимиты использования", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index 9b1f78f52a45..4c1758d269fb 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -272,6 +272,14 @@ export const dict = { "go.graph.freePill": "Big Pickle และโมเดลฟรี", "go.graph.go": "Go", "go.graph.label": "คำขอต่อ 5 ชั่วโมง", + "go.graph.period": "การใช้งาน", + "go.graph.model": "โมเดล", + "go.graph.requests": "requests โดยประมาณ / 5 ชม.", + "go.graph.allowance": "ปริมาณการใช้งานรายเดือน", + "go.graph.new": "ใหม่", + "go.graph.scale": "มาตราส่วนจำนวนคำขอแบบไม่เป็นเชิงเส้น", + "go.graph.showAll": "ดูโมเดลทั้งหมด {{count}} โมเดล", + "go.graph.showLess": "แสดงโมเดลน้อยลง", "go.graph.limitedRegions": "เฉพาะบางภูมิภาค", "go.graph.limitedTime": "ช่วงเวลาจำกัด", "go.graph.usageLimits": "ขีดจำกัดการใช้งาน", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index 49f7a788cb7b..3748ba8d8ade 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -275,6 +275,14 @@ export const dict = { "go.graph.freePill": "Big Pickle ve ücretsiz modeller", "go.graph.go": "Go", "go.graph.label": "5 saat başına istekler", + "go.graph.period": "Kullanım", + "go.graph.model": "Model", + "go.graph.requests": "Tahmini istek / 5 saat", + "go.graph.allowance": "Aylık kullanım", + "go.graph.new": "Yeni", + "go.graph.scale": "Doğrusal olmayan istek ölçeği", + "go.graph.showAll": "{{count}} modelin tümünü göster", + "go.graph.showLess": "Daha az model göster", "go.graph.limitedRegions": "sınırlı bölgeler", "go.graph.limitedTime": "sınırlı süre", "go.graph.usageLimits": "Kullanım limitleri", diff --git a/packages/console/app/src/i18n/uk.ts b/packages/console/app/src/i18n/uk.ts index d3002ad84854..0c23398ce9f6 100644 --- a/packages/console/app/src/i18n/uk.ts +++ b/packages/console/app/src/i18n/uk.ts @@ -273,6 +273,14 @@ export const dict = { "go.graph.freePill": "Big Pickle та безкоштовні моделі", "go.graph.go": "Go", "go.graph.label": "Запитів за 5 годин", + "go.graph.period": "Використання", + "go.graph.model": "Модель", + "go.graph.requests": "Оцінка запитів / 5 год", + "go.graph.allowance": "Використання за місяць", + "go.graph.new": "Нове", + "go.graph.scale": "Нелінійна шкала кількості запитів", + "go.graph.showAll": "Показати всі моделі ({{count}})", + "go.graph.showLess": "Показати менше моделей", "go.graph.limitedRegions": "обмежені регіони", "go.graph.limitedTime": "обмежений час", "go.graph.usageLimits": "Ліміти використання", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index ec6173e2a29a..887911f04bd6 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -262,6 +262,14 @@ export const dict = { "go.graph.freePill": "Big Pickle 和免费模型", "go.graph.go": "Go", "go.graph.label": "每 5 小时请求数", + "go.graph.period": "使用额度", + "go.graph.model": "模型", + "go.graph.requests": "预估请求数 / 5 小时", + "go.graph.allowance": "每月使用额度", + "go.graph.new": "新", + "go.graph.scale": "请求数采用非线性刻度", + "go.graph.showAll": "查看全部 {{count}} 个模型", + "go.graph.showLess": "收起模型", "go.graph.limitedRegions": "仅限部分地区", "go.graph.limitedTime": "限时", "go.graph.usageLimits": "使用限制", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index c088b268d8cf..742cd3b1da87 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -261,6 +261,14 @@ export const dict = { "go.graph.freePill": "Big Pickle 與免費模型", "go.graph.go": "Go", "go.graph.label": "每 5 小時請求數", + "go.graph.period": "使用額度", + "go.graph.model": "模型", + "go.graph.requests": "預估請求次數 / 5 小時", + "go.graph.allowance": "每月使用額度", + "go.graph.new": "新", + "go.graph.scale": "請求數採用非線性刻度", + "go.graph.showAll": "查看全部 {{count}} 個模型", + "go.graph.showLess": "收起模型", "go.graph.limitedRegions": "僅限部分地區", "go.graph.limitedTime": "限時", "go.graph.usageLimits": "使用限制", diff --git a/packages/console/app/src/lib/language.ts b/packages/console/app/src/lib/language.ts index a196f663db4c..773f63d61508 100644 --- a/packages/console/app/src/lib/language.ts +++ b/packages/console/app/src/lib/language.ts @@ -134,6 +134,34 @@ const DOCS_LOCALE = { "zh-tw": "zht", } as const satisfies Record +// Heading IDs from the localized Go documentation. +const GO_USAGE_LIMITS = { + en: "usage-limits", + zh: "使用限制", + zht: "使用限制", + ko: "사용-한도", + de: "nutzungslimits", + es: "límites-de-uso", + fr: "limites-dutilisation", + it: "limiti-di-utilizzo", + da: "forbrugsgrænser", + ja: "利用制限", + pl: "limity-użycia", + ru: "лимиты-использования", + uk: "usage-limits", + ar: "حدود-الاستخدام", + no: "bruksgrenser", + br: "limites-de-uso", + th: "usage-limits", + tr: "kullanım-limitleri", +} satisfies Record + +export function goUsageLimits(locale: Locale) { + // No Ukrainian Go docs yet; the explicit English path overrides the locale cookie. + if (locale === "uk") return "/docs/en/go/#usage-limits" + return docs(locale, `/docs/go/#${GO_USAGE_LIMITS[locale]}`) +} + function suffix(pathname: string) { const index = pathname.search(/[?#]/) if (index === -1) { diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index 59bbea8fcbfc..f80bde082499 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -17,6 +17,7 @@ import { IconMiniMax, IconMiMo, IconZai, IconAlibaba, IconDeepSeek } from "~/com import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" import { LocaleLinks } from "~/component/locale-links" +import { goUsageLimits } from "~/lib/language" const checkLoggedIn = query(async () => { "use server" @@ -201,7 +202,7 @@ export default function Home() {
    - +
    diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index bb085bd4a4f0..1c8354ab3400 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -124,22 +124,67 @@ OpenCode Go هو اشتراك منخفض التكلفة بقيمة **$10/شهر ## حدود الاستخدام -يتضمن OpenCode Go الحدود الأساسية التالية: +تُحدَّد حدود الاستخدام كمبالغ شهرية بالدولار. يوضح الجدول أدناه الحد الشهري وتكاليف tokens لكل نموذج. -- **حد 5 ساعات** — استخدام بقيمة $12 -- **الحد الأسبوعي** — استخدام بقيمة $30 -- **الحد الشهري** — استخدام بقيمة $60 +لكل نموذج حدود الاستخدام التالية: 5 ساعات — 20% من الحد الشهري؛ وأسبوعي — 50%؛ وشهري — 100%. -يختلف الحد الفعلي حسب النموذج؛ راجع الجدول أدناه. +على سبيل المثال، إذا كان الحد الشهري لنموذج ما $60، فيمكنك إنفاق ما يصل إلى: -تُحدَّد الحدود بالقيمة بالدولار. وهذا يعني أن عدد طلباتك الفعلي يعتمد على النموذج الذي تستخدمه. تتيح النماذج الأقل تكلفة مثل MiMo-V2.5 عددًا أكبر من الطلبات، بينما تتيح النماذج الأعلى تكلفة مثل GLM-5.2 عددًا أقل. +- **حد 5 ساعات** — $12 من الاستخدام +- **الحد الأسبوعي** — $30 من الاستخدام +- **الحد الشهري** — $60 من الاستخدام -يوضح الجدول أدناه عددًا تقديريًا للطلبات بناءً على أنماط استخدام Go المعتادة: +أسعار tokens مُدرجة لكل 1M tokens. + +| النموذج | الإدخال | الإخراج | القراءة المخزنة | الكتابة المخزنة | الحد الشهري | +| --------------------------------------- | ------- | ------- | --------------- | --------------- | --------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | + +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** ساعات Peak هي 01:00-04:00 و06:00-10:00 UTC من الاثنين إلى الجمعة؛ وجميع الساعات الأخرى، بما في ذلك عطلات نهاية الأسبوع، Off-Peak. [اعرف المزيد](https://api-docs.deepseek.com/quick_start/pricing/). + +**DeepSeek V4 Flash Vision Exp:** يتم تحويل الصور إلى رموز بناءً على أبعادها، وتُحتسب كرموز إدخال إلى جانب رموز النص. [اعرف المزيد](https://api-docs.deepseek.com/quick_start/pricing/). + +### الطلبات المقدّرة + +يوضح الجدول أدناه عددًا تقديريًا للطلبات استنادًا إلى أنماط استخدام Go المعتادة: | Model | الطلبات لكل 5 ساعات | الطلبات في الأسبوع | الطلبات في الشهر | | ---------------------------- | ------------------- | ------------------ | ---------------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| Omen Alpha | 11,600 | 29,000 | 57,900 | | GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | @@ -164,7 +209,8 @@ OpenCode Go هو اشتراك منخفض التكلفة بقيمة **$10/شهر | DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | | Hy4 preview | 1,350 | 3,380 | 6,770 | | Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | تستخدم التقديرات أعداد tokens التالية لكل طلب؛ ويختلف الاستخدام الفعلي. @@ -193,50 +239,6 @@ OpenCode Go هو اشتراك منخفض التكلفة بقيمة **$10/شهر - MiMo-V2.5-Pro — ‏790 input، و86,000 cached، و305 output tokens لكل طلب - Omen Alpha — ‏300 input، و40,000 cached، و100 output tokens لكل طلب -تستند التقديرات أيضًا إلى الأسعار التالية لكل 1M tokens والاستخدام الشهري المتضمن مع كل نموذج: - -| النموذج | الإدخال | الإخراج | القراءة المخزنة | الكتابة المخزنة | الاستخدام | -| --------------------------------------- | ------- | ------- | --------------- | --------------- | --------- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** ساعات Peak هي 01:00-04:00 و06:00-10:00 UTC من الاثنين إلى الجمعة؛ وجميع الساعات الأخرى، بما في ذلك عطلات نهاية الأسبوع، Off-Peak. [اعرف المزيد](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** يتم تحويل الصور إلى رموز بناءً على أبعادها، وتُحتسب كرموز إدخال إلى جانب رموز النص. [اعرف المزيد](https://api-docs.deepseek.com/quick_start/pricing/). - يمكنك تتبّع استخدامك الحالي في **console**. :::tip @@ -255,13 +257,13 @@ OpenCode Go هو اشتراك منخفض التكلفة بقيمة **$10/شهر ### لماذا يكون الاستخدام أقل لبعض النماذج -مع Go، تدفع $10 شهريًا، ونهدف لمعظم النماذج إلى منحك استخدامًا بقيمة تعادل 6 أضعاف هذا المبلغ. +مع Go، تدفع $10 شهريًا، ويختلف الاستخدام الشهري المتضمن حسب النموذج. -نحقق ذلك لمعظم النماذج من خلال الخصومات على الكميات الكبيرة وسعة GPU المحجوزة. ثم ننقل هذه الوفورات إليك من خلال معامل مضاعفة قدره 6. +نحقق ذلك لمعظم النماذج من خلال الخصومات على الكميات الكبيرة وسعة GPU المحجوزة. ثم ننقل هذه الوفورات إليك في صورة استخدام شهري أعلى. بالنسبة إلى بعض النماذج، لم تتح لنا فرصة التفاوض على خصم أو استضافتها بتكلفة أقل، إما لأن النموذج جديد أو لأن أسعاره العامة مخفضة بالفعل. -بالنسبة إلى هذه النماذج، لا تزال تحصل على استخدام يزيد قليلًا عما ستحصل عليه إذا دفعت لمزودي النماذج مباشرةً؛ ولهذا يكون معامل مضاعفة استخدامها أقل في الجدول أعلاه. +بالنسبة إلى هذه النماذج، لا تزال تحصل على استخدام يزيد قليلًا عما ستحصل عليه إذا دفعت لمزودي النماذج مباشرةً؛ ولهذا يكون الاستخدام الشهري المتضمن معها أقل. --- diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index 58feac308c7b..6ef94f809717 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -134,22 +134,67 @@ istražili. Povezani izvještaji prate ispravke i zaobilazna rješenja. ## Ograničenja upotrebe -OpenCode Go uključuje sljedeća osnovna ograničenja: +Ograničenja upotrebe definirana su kao mjesečni iznosi u dolarima. Tabela ispod prikazuje mjesečno ograničenje i troškove tokena za svaki model. + +Svaki model ima sljedeća ograničenja upotrebe: 5 sati — 20% mjesečnog ograničenja; sedmično — 50%; i mjesečno — 100%. + +Na primjer, ako model ima mjesečno ograničenje od $60, možete potrošiti do: - **Ograničenje od 5 sati** — $12 potrošnje - **Sedmično ograničenje** — $30 potrošnje - **Mjesečno ograničenje** — $60 potrošnje -Efektivna potrošnja razlikuje se po modelu; pogledajte tabelu ispod. +Cijene tokena navedene su za 1M tokena. + +| Model | Input | Output | Cached Read | Cached Write | Mjesečno ograničenje | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | --------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -Ograničenja su definisana u dolarskoj vrijednosti. To znači da vaš stvarni broj zahtjeva zavisi od modela koji koristite. Jeftiniji modeli poput MiMo-V2.5 omogućavaju više zahtjeva, dok skuplji modeli poput GLM-5.2 omogućavaju manje. +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak sati su 01:00-04:00 i 06:00-10:00 UTC od ponedjeljka do petka; svi ostali sati, uključujući vikende, su Off-Peak. [Saznajte više](https://api-docs.deepseek.com/quick_start/pricing/). -Tabela ispod pruža procijenjeni broj zahtjeva na osnovu tipičnih obrazaca korištenja Go pretplate: +**DeepSeek V4 Flash Vision Exp:** Slike se pretvaraju u tokene na osnovu svojih dimenzija i naplaćuju kao ulazni tokeni zajedno s tekstualnim tokenima. [Saznajte više](https://api-docs.deepseek.com/quick_start/pricing/). + +### Procijenjeni broj zahtjeva + +Tabela ispod daje procijenjeni broj zahtjeva na osnovu tipičnih obrazaca korištenja Go-a: | Model | zahtjeva na 5 sati | zahtjeva sedmično | zahtjeva mjesečno | | ---------------------------- | ------------------ | ----------------- | ----------------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| Omen Alpha | 11,600 | 29,000 | 57,900 | | GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | @@ -174,7 +219,8 @@ Tabela ispod pruža procijenjeni broj zahtjeva na osnovu tipičnih obrazaca kori | DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | | Hy4 preview | 1,350 | 3,380 | 6,770 | | Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Procjene koriste sljedeći broj tokena po zahtjevu; stvarna potrošnja varira. @@ -203,50 +249,6 @@ Procjene koriste sljedeći broj tokena po zahtjevu; stvarna potrošnja varira. - MiMo-V2.5-Pro — 790 ulaznih, 86,000 keširanih, 305 izlaznih tokena po zahtjevu - Omen Alpha — 300 ulaznih, 40.000 keširanih, 100 izlaznih tokena po zahtjevu -Procjene se također zasnivaju na sljedećim cijenama po 1M tokena i mjesečnoj potrošnji uključenoj uz svaki model: - -| Model | Input | Output | Cached Read | Cached Write | Potrošnja | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | --------- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak sati su 01:00-04:00 i 06:00-10:00 UTC od ponedjeljka do petka; svi ostali sati, uključujući vikende, su Off-Peak. [Saznajte više](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** Slike se pretvaraju u tokene na osnovu svojih dimenzija i naplaćuju kao ulazni tokeni zajedno s tekstualnim tokenima. [Saznajte više](https://api-docs.deepseek.com/quick_start/pricing/). - Svoju trenutnu potrošnju možete pratiti u **konzoli**. :::tip @@ -267,13 +269,13 @@ nakon što dostignete ograničenja upotrebe umjesto blokiranja zahtjeva. ### Zašto neki modeli imaju manju uključenu potrošnju -Uz Go plaćate $10 mjesečno, a za većinu modela cilj nam je omogućiti vam potrošnju šest puta veću od tog iznosa. +Uz Go plaćate $10 mjesečno, a uključena mjesečna potrošnja razlikuje se po modelu. -Za većinu modela to postižemo količinskim popustima i rezervisanim GPU kapacitetom. Tu uštedu zatim prenosimo na vas primjenom faktora šest. +Za većinu modela to postižemo količinskim popustima i rezervisanim GPU kapacitetom. Tu uštedu zatim prenosimo na vas kroz veću mjesečnu potrošnju. Za neke modele još nismo imali priliku dogovoriti popust ili ih hostovati po nižoj cijeni, bilo zato što su modeli novi ili zato što su njihove javno objavljene cijene već snižene. -Za ove modele i dalje dobijate malo više nego da direktno plaćate provajderima modela; zato je njihov faktor potrošnje niži u gornjoj tabeli. +Za ove modele i dalje dobijate malo više nego da direktno plaćate provajderima modela; zato je njihova uključena mjesečna potrošnja manja. --- diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 663140b9b1da..6266d456653b 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -134,47 +134,93 @@ undersøgte. De linkede rapporter følger rettelser og løsninger. ## Forbrugsgrænser -OpenCode Go inkluderer følgende basisgrænser: +Forbrugsgrænser defineres som månedlige beløb i dollars. Tabellen nedenfor viser den månedlige grænse og tokenpriserne for hver model. + +Hver model har følgende forbrugsgrænser: 5 timer — 20 % af den månedlige grænse; ugentligt — 50 %; og månedligt — 100 %. + +Hvis en model for eksempel har en månedlig grænse på $60, kan du bruge op til: + +- **5-timers grænse** — $12 i forbrug +- **Ugentlig grænse** — $30 i forbrug +- **Månedlig grænse** — $60 i forbrug + +Tokenpriser er angivet pr. 1M tokens. + +| Model | Input | Output | Cached Read | Cached Write | Månedlig grænse | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -- **5-timers grænse** — forbrug for $12 -- **Ugentlig grænse** — forbrug for $30 -- **Månedlig grænse** — forbrug for $60 +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak-tiderne er 01:00-04:00 og 06:00-10:00 UTC fra mandag til fredag; alle andre tider, herunder weekender, er Off-Peak. [Læs mere](https://api-docs.deepseek.com/quick_start/pricing/). -Det effektive forbrug varierer efter model; se tabellen nedenfor. +**DeepSeek V4 Flash Vision Exp:** Billeder konverteres til tokens baseret på deres dimensioner og afregnes som inputtokens sammen med teksttokens. [Læs mere](https://api-docs.deepseek.com/quick_start/pricing/). -Grænserne er defineret i dollarværdi. Det betyder, at dit faktiske antal anmodninger afhænger af den model, du bruger. Billigere modeller som MiMo-V2.5 tillader flere anmodninger, mens dyrere modeller som GLM-5.2 tillader færre. +### Estimerede anmodninger -Tabellen nedenfor giver et estimeret antal anmodninger baseret på typiske Go-forbrugsmønstre: +Tabellen nedenfor viser et estimeret antal anmodninger baseret på typiske Go-forbrugsmønstre: | Model | anmodninger pr. 5 timer | anmodninger pr. uge | anmodninger pr. måned | -| ---------------------------- | ----------------------- | ------------------- | --------------------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| ---------------------------- | ------------------- | ----------------- | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Estimaterne bruger følgende antal tokens pr. anmodning; det faktiske forbrug varierer. @@ -203,50 +249,6 @@ Estimaterne bruger følgende antal tokens pr. anmodning; det faktiske forbrug va - MiMo-V2.5-Pro — 790 input, 86.000 cachelagrede, 305 output-tokens pr. anmodning - Omen Alpha — 300 input-, 40.000 cachede, 100 output-tokens pr. anmodning -Estimaterne er også baseret på følgende priser pr. 1M tokens og det månedlige forbrug, der er inkluderet med hver model: - -| Model | Input | Output | Cached Read | Cached Write | Forbrug | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak-tiderne er 01:00-04:00 og 06:00-10:00 UTC fra mandag til fredag; alle andre tider, herunder weekender, er Off-Peak. [Læs mere](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** Billeder konverteres til tokens baseret på deres dimensioner og afregnes som inputtokens sammen med teksttokens. [Læs mere](https://api-docs.deepseek.com/quick_start/pricing/). - Du kan spore dit nuværende forbrug i **konsollen**. :::tip @@ -267,13 +269,13 @@ når du har nået dine forbrugsgrænser, i stedet for at blokere anmodninger. ### Hvorfor nogle modeller har lavere forbrug -Med Go betaler du $10/måned, og for de fleste modeller sigter vi mod at give dig 6x så meget i forbrug. +Med Go betaler du $10/måned, og det inkluderede månedlige forbrug varierer efter model. -For de fleste modeller gør vi dette muligt gennem mængderabatter og reserveret GPU-kapacitet. Vi giver dig derefter disse besparelser videre gennem 6x-multiplikatoren. +For de fleste modeller gør vi dette muligt gennem mængderabatter og reserveret GPU-kapacitet. Vi giver dig derefter disse besparelser videre i form af mere månedligt forbrug. For nogle modeller har vi endnu ikke haft mulighed for at forhandle en rabat eller hoste dem til en lavere pris, enten fordi modellen er ny, eller fordi dens offentlige pris allerede er nedsat. -Med disse modeller får du stadig lidt mere, end hvis du betalte modeludbyderne direkte. Derfor er deres forbrugsmultiplikator lavere i tabellen ovenfor. +Med disse modeller får du stadig lidt mere, end hvis du betalte modeludbyderne direkte. Derfor er deres inkluderede månedlige forbrug lavere. --- diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index cac9b3d6e94e..2921f3716db5 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -126,47 +126,93 @@ oder sie ist unvollständig. Die verlinkten Berichte dokumentieren Fixes und Pro ## Nutzungslimits -OpenCode Go beinhaltet die folgenden Basislimits: +Nutzungslimits werden als monatliche Dollarbeträge festgelegt. Die folgende Tabelle zeigt das monatliche Limit und die Tokenpreise für jedes Modell. + +Für jedes Modell gelten folgende Nutzungslimits: 5 Stunden — 20 % des monatlichen Limits; wöchentlich — 50 %; und monatlich — 100 %. + +Wenn ein Modell beispielsweise ein monatliches Limit von $60 hat, kannst du bis zu folgende Beträge nutzen: + +- **5-Stunden-Limit** — Nutzung im Wert von $12 +- **Wöchentliches Limit** — Nutzung im Wert von $30 +- **Monatliches Limit** — Nutzung im Wert von $60 + +Tokenpreise gelten pro 1M Tokens. + +| Model | Input | Output | Cached Read | Cached Write | Monatliches Limit | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -- **5-Stunden-Limit** — 12 $ Nutzung -- **Wöchentliches Limit** — 30 $ Nutzung -- **Monatliches Limit** — 60 $ Nutzung +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Die Peak-Zeiten sind montags bis freitags von 01:00-04:00 und 06:00-10:00 UTC; alle anderen Zeiten, einschließlich der Wochenenden, sind Off-Peak. [Mehr erfahren](https://api-docs.deepseek.com/quick_start/pricing/). -Das effektive Kontingent variiert je nach Modell; siehe die Tabelle unten. +**DeepSeek V4 Flash Vision Exp:** Bilder werden anhand ihrer Abmessungen in Tokens umgewandelt und zusammen mit Text-Tokens als Input-Tokens abgerechnet. [Mehr erfahren](https://api-docs.deepseek.com/quick_start/pricing/). -Limits sind in Dollarwerten definiert. Das bedeutet, dass die tatsächliche Anzahl deiner Anfragen von dem von dir genutzten Modell abhängt. Günstigere Modelle wie MiMo-V2.5 erlauben mehr Anfragen, während teurere Modelle wie GLM-5.2 weniger erlauben. +### Geschätzte Anfragen -Die folgende Tabelle zeigt eine geschätzte Anzahl von Anfragen basierend auf typischen Go-Nutzungsmustern: +Die folgende Tabelle enthält eine geschätzte Anzahl von Anfragen basierend auf typischen Go-Nutzungsmustern: | Model | Anfragen pro 5 Stunden | Anfragen pro Woche | Anfragen pro Monat | -| ---------------------------- | ---------------------- | ------------------ | ------------------ | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| ---------------------------- | ------------------- | ----------------- | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Die Schätzungen verwenden die folgenden Token-Anzahlen pro Anfrage; die tatsächliche Nutzung variiert. @@ -195,50 +241,6 @@ Die Schätzungen verwenden die folgenden Token-Anzahlen pro Anfrage; die tatsäc - MiMo-V2.5-Pro — 790 Input-, 86.000 Cached-, 305 Output-Tokens pro Anfrage - Omen Alpha — 300 Input-, 40.000 Cached-, 100 Output-Tokens pro Anfrage -Die Schätzungen basieren außerdem auf den folgenden Preisen pro 1M Tokens und der monatlichen Nutzung, die bei jedem Modell enthalten ist: - -| Model | Input | Output | Cached Read | Cached Write | Nutzung | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Die Peak-Zeiten sind montags bis freitags von 01:00-04:00 und 06:00-10:00 UTC; alle anderen Zeiten, einschließlich der Wochenenden, sind Off-Peak. [Mehr erfahren](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** Bilder werden anhand ihrer Abmessungen in Tokens umgewandelt und zusammen mit Text-Tokens als Input-Tokens abgerechnet. [Mehr erfahren](https://api-docs.deepseek.com/quick_start/pricing/). - Du kannst deine aktuelle Nutzung in der **Console** verfolgen. :::tip @@ -257,13 +259,13 @@ Wenn du auch Guthaben auf deinem Zen-Konto hast, kannst du in der Console die Op ### Warum einige Modelle weniger Nutzung bieten -Mit Go zahlst du $10/Monat, und bei den meisten Modellen ist unser Ziel, dir dafür das Sechsfache dieses Betrags als Nutzungsguthaben zu bieten. +Mit Go zahlst du $10/Monat, und die enthaltene monatliche Nutzung variiert je nach Modell. -Bei den meisten Modellen ermöglichen wir dies durch Mengenrabatte und reservierte GPU-Kapazität. Diese Ersparnisse geben wir dann über den 6x-Multiplikator an dich weiter. +Bei den meisten Modellen ermöglichen wir dies durch Mengenrabatte und reservierte GPU-Kapazität. Diese Ersparnisse geben wir dann in Form von zusätzlicher monatlicher Nutzung an dich weiter. Bei einigen Modellen hatten wir bisher keine Gelegenheit, einen Rabatt auszuhandeln oder sie kostengünstiger zu hosten, entweder weil das jeweilige Modell neu oder sein öffentlicher Preis bereits rabattiert ist. -Bei diesen Modellen erhältst du immer noch etwas mehr, als wenn du die Modellanbieter direkt bezahlen würdest. Deshalb ist ihr Nutzungsmultiplikator in der Tabelle oben niedriger. +Bei diesen Modellen erhältst du immer noch etwas mehr, als wenn du die Modellanbieter direkt bezahlen würdest. Deshalb ist ihre enthaltene monatliche Nutzung geringer. --- diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index 4f120adb5dea..304cbec24086 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -134,22 +134,67 @@ investigamos. Los informes enlazados permiten seguir las correcciones y las solu ## Límites de uso -OpenCode Go incluye los siguientes límites base: +Los límites de uso se definen como importes mensuales en dólares. La siguiente tabla muestra el límite mensual y los costes de tokens de cada modelo. + +Cada modelo tiene los siguientes límites de uso: 5 horas — 20 % del límite mensual; semanal — 50 %; y mensual — 100 %. + +Por ejemplo, si un modelo tiene un límite mensual de $60, puedes gastar hasta: - **Límite de 5 horas** — $12 de uso - **Límite semanal** — $30 de uso - **Límite mensual** — $60 de uso -La asignación efectiva varía según el modelo; consulta la tabla siguiente. +Los precios de tokens se indican por 1M tokens. + +| Modelo | Entrada | Salida | Lectura en caché | Escritura en caché | Límite mensual | +| --------------------------------------- | ------- | ------ | ---------------- | ------------------ | ---- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -Los límites se definen en valor en dólares. Esto significa que tu cantidad real de peticiones depende del modelo que uses. Los modelos más económicos como MiMo-V2.5 permiten más peticiones, mientras que los modelos de mayor costo como GLM-5.2 permiten menos. +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Las horas Peak son 01:00-04:00 y 06:00-10:00 UTC, de lunes a viernes; todas las demás horas, incluidos los fines de semana, son Off-Peak. [Más información](https://api-docs.deepseek.com/quick_start/pricing/). -La siguiente tabla proporciona una cantidad estimada de peticiones basada en los patrones típicos de uso de Go: +**DeepSeek V4 Flash Vision Exp:** Las imágenes se convierten en tokens según sus dimensiones y se facturan como tokens de entrada junto con los tokens de texto. [Más información](https://api-docs.deepseek.com/quick_start/pricing/). + +### Peticiones estimadas + +La siguiente tabla ofrece 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 | | ---------------------------- | ---------------------- | --------------------- | ------------------ | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| Omen Alpha | 11,600 | 29,000 | 57,900 | | GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | @@ -174,7 +219,8 @@ La siguiente tabla proporciona una cantidad estimada de peticiones basada en los | DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | | Hy4 preview | 1,350 | 3,380 | 6,770 | | Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Las estimaciones usan las siguientes cantidades de tokens por petición; el uso real varía. @@ -203,50 +249,6 @@ Las estimaciones usan las siguientes cantidades de tokens por petición; el uso - MiMo-V2.5-Pro — 790 tokens de entrada, 86,000 en caché, 305 tokens de salida por petición - Omen Alpha — 300 tokens de entrada, 40,000 en caché, 100 tokens de salida por petición -Las estimaciones también se basan en los siguientes precios por 1M tokens y en el uso mensual incluido con cada modelo: - -| Modelo | Entrada | Salida | Lectura en caché | Escritura en caché | Uso | -| --------------------------------------- | ------- | ------ | ---------------- | ------------------ | ---- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Las horas Peak son 01:00-04:00 y 06:00-10:00 UTC, de lunes a viernes; todas las demás horas, incluidos los fines de semana, son Off-Peak. [Más información](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** Las imágenes se convierten en tokens según sus dimensiones y se facturan como tokens de entrada junto con los tokens de texto. [Más información](https://api-docs.deepseek.com/quick_start/pricing/). - Puedes realizar un seguimiento de tu uso actual en la **consola**. :::tip @@ -267,13 +269,13 @@ después de que hayas alcanzado tus límites de uso en lugar de bloquear las pet ### Por qué algunos modelos incluyen menos uso -Con Go, pagas $10/mes y, para la mayoría de los modelos, nuestro objetivo es ofrecerte un uso equivalente a 6 veces esa cantidad. +Con Go, pagas $10/mes y el uso mensual incluido varía según el modelo. -Para la mayoría de los modelos, lo conseguimos mediante descuentos por volumen y capacidad de GPU reservada. Ese ahorro se traduce en un multiplicador de 6x para ti. +Para la mayoría de los modelos, lo conseguimos mediante descuentos por volumen y capacidad de GPU reservada. Te trasladamos ese ahorro en forma de un mayor uso mensual. Para algunos modelos, todavía no hemos tenido la oportunidad de negociar un descuento o alojarlos a un coste menor, ya sea porque el modelo es nuevo o porque su precio público ya incluye un descuento. -Con estos modelos, aun así obtienes un poco más que si pagaras directamente a los proveedores de modelos; por eso su multiplicador de uso es menor en la tabla anterior. +Con estos modelos, aun así obtienes un poco más que si pagaras directamente a los proveedores de modelos; por eso su uso mensual incluido es menor. --- diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 06c4452afc72..05f472ede29e 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -124,47 +124,93 @@ ou ne les prennent en charge que partiellement. Les rapports associés permetten ## Limites d'utilisation -OpenCode Go inclut les limites de base suivantes : +Les limites d’utilisation sont définies sous forme de montants mensuels en dollars. Le tableau ci-dessous indique la limite mensuelle et les prix des tokens pour chaque modèle. + +Chaque modèle est soumis aux limites d’utilisation suivantes : 5 heures — 20 % de la limite mensuelle ; hebdomadaire — 50 % ; et mensuelle — 100 %. + +Par exemple, si un modèle a une limite mensuelle de $60, vous pouvez utiliser jusqu’à : + +- **Limite de 5 heures** — $12 d’utilisation +- **Limite hebdomadaire** — $30 d’utilisation +- **Limite mensuelle** — $60 d’utilisation + +Les prix des tokens sont indiqués par million de tokens. + +| Modèle | Input | Output | Cached Read | Cached Write | Limite mensuelle | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -- **Limite de 5 heures** — 12 $ d'utilisation -- **Limite hebdomadaire** — 30 $ d'utilisation -- **Limite mensuelle** — 60 $ d'utilisation +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Les heures Peak sont 01:00-04:00 et 06:00-10:00 UTC, du lundi au vendredi ; toutes les autres heures, y compris le week-end, sont Off-Peak. [En savoir plus](https://api-docs.deepseek.com/quick_start/pricing/). -L'allocation effective varie selon le modèle ; consultez le tableau ci-dessous. +**DeepSeek V4 Flash Vision Exp:** Les images sont converties en tokens selon leurs dimensions et facturées comme tokens d’entrée avec les tokens de texte. [En savoir plus](https://api-docs.deepseek.com/quick_start/pricing/). -Les limites sont définies en valeur monétaire (dollars). Cela signifie que votre nombre réel de requêtes dépend du modèle que vous utilisez. Les modèles moins chers comme MiMo-V2.5 permettent plus de requêtes, tandis que les modèles plus coûteux comme GLM-5.2 en permettent moins. +### Requêtes estimées -Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur des modèles d'utilisation typiques de Go : +Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur les habitudes d’utilisation typiques de Go : | Model | requêtes par 5 heures | requêtes par semaine | requêtes par mois | -| ---------------------------- | --------------------- | -------------------- | ----------------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| ---------------------------- | ------------------- | ----------------- | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Les estimations utilisent les nombres de tokens suivants par requête ; l'utilisation réelle varie. @@ -193,50 +239,6 @@ Les estimations utilisent les nombres de tokens suivants par requête ; l'utilis - MiMo-V2.5-Pro — 790 tokens en entrée, 86,000 en cache, 305 tokens en sortie par requête - Omen Alpha — 300 tokens en entrée, 40 000 en cache, 100 tokens en sortie par requête -Les estimations sont également basées sur les prix suivants par 1M tokens et sur l'utilisation mensuelle incluse avec chaque modèle : - -| Modèle | Input | Output | Cached Read | Cached Write | Utilisation | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ----------- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Les heures Peak sont 01:00-04:00 et 06:00-10:00 UTC, du lundi au vendredi ; toutes les autres heures, y compris le week-end, sont Off-Peak. [En savoir plus](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** Les images sont converties en tokens selon leurs dimensions et facturées comme tokens d’entrée avec les tokens de texte. [En savoir plus](https://api-docs.deepseek.com/quick_start/pricing/). - Vous pouvez suivre votre utilisation actuelle dans la **console**. :::tip @@ -255,13 +257,13 @@ Si vous avez également des crédits sur votre solde Zen, vous pouvez activer l' ### Pourquoi certains modèles offrent un volume d'utilisation inférieur -Avec Go, vous payez 10 $/mois et, pour la plupart des modèles, nous cherchons à vous offrir un volume d'utilisation équivalant à 6 fois ce montant. +Avec Go, vous payez 10 $/mois, et l’utilisation mensuelle incluse varie selon le modèle. -Pour la plupart des modèles, nous y parvenons grâce à des remises sur volume et à une capacité GPU réservée. Nous vous faisons ensuite bénéficier de ces économies grâce à un coefficient multiplicateur de 6. +Pour la plupart des modèles, nous y parvenons grâce à des remises sur volume et à une capacité GPU réservée. Nous vous faisons ensuite bénéficier de ces économies sous la forme d’une utilisation mensuelle plus élevée. Pour certains modèles, nous n'avons pas encore eu l'occasion de négocier une remise ou de les héberger à moindre coût, soit parce que le modèle est nouveau, soit parce que son tarif public est déjà réduit. -Pour ces modèles, vous obtenez tout de même un peu plus que si vous payiez directement les fournisseurs de modèles ; c'est pourquoi leur multiplicateur d'utilisation est plus faible dans le tableau ci-dessus. +Pour ces modèles, vous obtenez tout de même un peu plus que si vous payiez directement les fournisseurs de modèles ; c’est pourquoi leur utilisation mensuelle incluse est plus faible. --- diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index ea3dfd047a5f..afa597baae52 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -134,22 +134,69 @@ investigated. The linked reports track fixes and workarounds. ## Usage limits -OpenCode Go includes the following base limits: +Usage limits are defined as monthly dollar amounts. The table below shows the +monthly limit and token costs for each model. -- **5 hour limit** — $12 of usage +Each model has the following usage limits: 5-hour — 20% of the monthly limit; +weekly — 50%; and monthly — 100%. + +For example, if a model has a $60 monthly limit, you can spend up to: + +- **5-hour limit** — $12 of usage - **Weekly limit** — $30 of usage - **Monthly limit** — $60 of usage -Effective allowance varies by model; see the table below. +Token prices are per 1M tokens. + +| Model | Input | Output | Cached Read | Cached Write | Monthly limit | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -Limits are defined in dollar value. This means your actual request count depends on the model you use. Cheaper models like MiMo-V2.5 allow for more requests, while higher-cost models like GLM-5.2 allow for fewer. +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak hours are 01:00-04:00 and 06:00-10:00 UTC, Monday through Friday; all other hours, including weekends, are Off-Peak. [Learn more](https://api-docs.deepseek.com/quick_start/pricing/). + +**DeepSeek V4 Flash Vision Exp:** Images are converted into tokens based on their dimensions and billed as input tokens alongside text tokens. [Learn more](https://api-docs.deepseek.com/quick_start/pricing/). + +### Estimated requests 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 | | ---------------------------- | ------------------- | ----------------- | ------------------ | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| Omen Alpha | 11,600 | 29,000 | 57,900 | | GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | @@ -174,7 +221,8 @@ The table below provides an estimated request count based on typical Go usage pa | DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | | Hy4 preview | 1,350 | 3,380 | 6,770 | | Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | The estimates use the following token counts per request; actual usage varies. @@ -203,50 +251,6 @@ The estimates use the following token counts per request; actual usage varies. - Hy3 — 830 input, 71,500 cached, 295 output tokens per request - Omen Alpha — 300 input, 40,000 cached, 100 output tokens per request -The estimates are also based on the following prices per 1M tokens and the monthly usage included with each model: - -| Model | Input | Output | Cached Read | Cached Write | Usage | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ----- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak hours are 01:00-04:00 and 06:00-10:00 UTC, Monday through Friday; all other hours, including weekends, are Off-Peak. [Learn more](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** Images are converted into tokens based on their dimensions and billed as input tokens alongside text tokens. [Learn more](https://api-docs.deepseek.com/quick_start/pricing/). - You can track your current usage in the **console**. :::tip @@ -267,13 +271,13 @@ after you've reached your usage limits instead of blocking requests. ### Why some models have lower usage -With Go, you pay $10/month and, for most models, we aim to give you 6x that in usage. +With Go, you pay $10/month, and the included monthly usage varies by model. -For most models, we make this work through bulk discounts and reserved GPU capacity. We then pass those savings on to you through the 6x multiplier. +For most models, we make this work through bulk discounts and reserved GPU capacity. We then pass those savings on to you as higher monthly usage. For some models, we haven't had the opportunity to negotiate a discount or host them at a lower cost, either because the model is new or because their public pricing is already discounted. -For these models, you still get a little more than if you paid the model providers directly; this is why their usage mulitplier is lower in the table above. +For these models, you still get a little more than if you paid the model providers directly; this is why their included monthly usage is lower. --- diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index 581a46b244dc..154031e0e1bf 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -132,47 +132,93 @@ o le supportano solo parzialmente. Le segnalazioni collegate consentono di segui ## Limiti di utilizzo -OpenCode Go include i seguenti limiti di base: +I limiti di utilizzo sono definiti come importi mensili in dollari. La tabella seguente mostra il limite mensile e i prezzi dei token per ciascun modello. + +Ogni modello ha i seguenti limiti di utilizzo: 5 ore — 20% del limite mensile; settimanale — 50%; mensile — 100%. + +Ad esempio, se un modello ha un limite mensile di $60, puoi utilizzare fino a: + +- **Limite di 5 ore** — $12 di utilizzo +- **Limite settimanale** — $30 di utilizzo +- **Limite mensile** — $60 di utilizzo + +I prezzi dei token sono indicati per 1M token. + +| Modello | Input | Output | Cached Read | Cached Write | Limite mensile | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -- **Limite di 5 ore** — 12 $ di utilizzo -- **Limite settimanale** — 30 $ di utilizzo -- **Limite mensile** — 60 $ di utilizzo +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Gli orari Peak sono 01:00-04:00 e 06:00-10:00 UTC, dal lunedì al venerdì; tutti gli altri orari, inclusi i fine settimana, sono Off-Peak. [Scopri di più](https://api-docs.deepseek.com/quick_start/pricing/). -La quota effettiva varia in base al modello; consulta la tabella seguente. +**DeepSeek V4 Flash Vision Exp:** Le immagini vengono convertite in token in base alle loro dimensioni e fatturate come token di input insieme ai token di testo. [Scopri di più](https://api-docs.deepseek.com/quick_start/pricing/). -I limiti sono definiti in valore in dollari. Questo significa che il conteggio effettivo delle richieste dipende dal modello utilizzato. Modelli più economici come MiMo-V2.5 consentono più richieste, mentre modelli più costosi come GLM-5.2 ne consentono di meno. +### Richieste stimate -La tabella seguente fornisce una stima del conteggio delle richieste in base a pattern di utilizzo tipici di Go: +La tabella seguente fornisce una stima del numero di richieste basata su pattern di utilizzo tipici di Go: | Model | richieste ogni 5 ore | richieste a settimana | richieste al mese | -| ---------------------------- | -------------------- | --------------------- | ----------------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| ---------------------------- | ------------------- | ----------------- | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Le stime utilizzano i seguenti conteggi di token per richiesta; l'utilizzo effettivo varia. @@ -201,50 +247,6 @@ Le stime utilizzano i seguenti conteggi di token per richiesta; l'utilizzo effet - MiMo-V2.5-Pro — 790 di input, 86.000 in cache, 305 token di output per richiesta - Omen Alpha — 300 token di input, 40.000 token in cache, 100 token di output per richiesta -Le stime si basano anche sui seguenti prezzi per 1M token e sull'utilizzo mensile incluso con ciascun modello: - -| Modello | Input | Output | Cached Read | Cached Write | Utilizzo | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | -------- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Gli orari Peak sono 01:00-04:00 e 06:00-10:00 UTC, dal lunedì al venerdì; tutti gli altri orari, inclusi i fine settimana, sono Off-Peak. [Scopri di più](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** Le immagini vengono convertite in token in base alle loro dimensioni e fatturate come token di input insieme ai token di testo. [Scopri di più](https://api-docs.deepseek.com/quick_start/pricing/). - Puoi monitorare il tuo utilizzo attuale nella **console**. :::tip @@ -265,13 +267,13 @@ dopo che avrai raggiunto i limiti di utilizzo invece di bloccare le richieste. ### Perché alcuni modelli hanno un utilizzo inferiore -Con Go, paghi $10/mese e, per la maggior parte dei modelli, puntiamo a offrirti un utilizzo pari a 6 volte tale importo. +Con Go, paghi $10/mese e l’utilizzo mensile incluso varia in base al modello. -Per la maggior parte dei modelli, ci riusciamo grazie a sconti sui volumi e capacità GPU riservata. Ti facciamo quindi beneficiare di questi risparmi tramite il moltiplicatore 6x. +Per la maggior parte dei modelli, ci riusciamo grazie a sconti sui volumi e capacità GPU riservata. Ti facciamo quindi beneficiare di questi risparmi offrendo un utilizzo mensile maggiore. Per alcuni modelli, non abbiamo ancora avuto l'opportunità di negoziare uno sconto o di ospitarli a un costo inferiore, perché il modello è nuovo oppure perché il prezzo pubblico è già scontato. -Per questi modelli, ottieni comunque un po' più di utilizzo rispetto a quanto otterresti pagando direttamente i provider dei modelli; per questo il loro moltiplicatore di utilizzo è più basso nella tabella precedente. +Per questi modelli, ottieni comunque un po’ più di utilizzo rispetto a quanto otterresti pagando direttamente i provider dei modelli; per questo il loro utilizzo mensile incluso è inferiore. --- diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index a7b402f0c7c7..3dd3c966582a 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -121,47 +121,93 @@ OpenCodeに加えて、以下のクライアントがOpenCode Goで正常に動 ## 利用制限 -OpenCode Goには以下の基本制限が含まれています: +利用制限は月額(ドル)で定義されています。以下の表は、各モデルの月間上限とトークン単価を示しています。 + +各モデルの利用制限は、5時間で月間上限の20%、週間で50%、月間で100%です。 + +たとえば、月間上限が$60のモデルでは、最大で以下の金額を利用できます。 + +- **5時間の制限** — $12の利用 +- **週間の制限** — $30の利用 +- **月間の制限** — $60の利用 + +トークン単価は100万トークンあたりです。 + +| Model | Input | Output | Cached Read | Cached Write | 月間上限 | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -- **5時間の制限** — 12ドル分の利用 -- **週間の制限** — 30ドル分の利用 -- **月間の制限** — 60ドル分の利用 +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak時間は月曜日から金曜日の01:00-04:00と06:00-10:00 UTCで、週末を含むそれ以外の時間はすべてOff-Peakです。[詳しく見る](https://api-docs.deepseek.com/quick_start/pricing/)。 -有効な利用枠はモデルによって異なります。下の表をご覧ください。 +**DeepSeek V4 Flash Vision Exp:** 画像はサイズに基づいてトークンに変換され、テキストトークンと合わせて入力トークンとして課金されます。 [詳しく見る](https://api-docs.deepseek.com/quick_start/pricing/)。 -制限はドル単位で定義されています。つまり、実際のリクエスト数は使用するモデルによって異なります。MiMo-V2.5のような安価なモデルではより多くのリクエストが可能ですが、GLM-5.2のような高コストのモデルではリクエスト数が少なくなります。 +### 推定リクエスト数 -以下の表は、一般的なGoの利用パターンに基づいた推定リクエスト数を示しています: +以下の表は、一般的なGoの利用パターンに基づく推定リクエスト数を示しています。 | Model | 5時間あたりのリクエスト数 | 週間リクエスト数 | 月間リクエスト数 | -| ---------------------------- | ------------------------- | ---------------- | ---------------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| ---------------------------- | ------------------- | ----------------- | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | 推定値には、リクエストあたり以下のトークン数を使用しています。実際の使用量は異なります。 @@ -190,50 +236,6 @@ OpenCode Goには以下の基本制限が含まれています: - MiMo-V2.5-Pro — リクエストあたり 入力 790トークン、キャッシュ 86,000トークン、出力 305トークン - Omen Alpha — リクエストあたり 入力 300トークン、キャッシュ 40,000トークン、出力 100トークン -推定値は、100万トークンあたりの以下の価格と、各モデルに含まれる月間利用枠にも基づいています: - -| Model | Input | Output | Cached Read | Cached Write | Usage | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ----- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak時間は月曜日から金曜日の01:00-04:00と06:00-10:00 UTCで、週末を含むそれ以外の時間はすべてOff-Peakです。[詳しく見る](https://api-docs.deepseek.com/quick_start/pricing/)。 - -**DeepSeek V4 Flash Vision Exp:** 画像はサイズに基づいてトークンに変換され、テキストトークンと合わせて入力トークンとして課金されます。 [詳しく見る](https://api-docs.deepseek.com/quick_start/pricing/)。 - 現在の利用状況は**コンソール**で追跡できます。 :::tip @@ -252,13 +254,13 @@ Zen残高にクレジットがある場合は、コンソールで**Use balance* ### 一部のモデルの利用枠が少ない理由 -Goでは月額$10を支払い、ほとんどのモデルでその6倍の利用枠を提供することを目指しています。 +Goでは月額$10を支払い、含まれる月間利用枠はモデルによって異なります。 -ほとんどのモデルでは、ボリュームディスカウントと予約済みのGPUキャパシティによってこれを実現しています。そして、その節約分を6倍の倍率で還元しています。 +ほとんどのモデルでは、ボリュームディスカウントと予約済みのGPUキャパシティによってこれを実現しています。そして、その節約分を月間利用枠の増加という形で還元しています。 一部のモデルでは、モデルが新しい、または公開価格がすでに割引されているため、割引を交渉したり、より低コストでホストしたりする機会がまだありません。 -これらのモデルでも、モデルプロバイダーに直接支払う場合より少し多く利用できます。そのため、上の表では利用枠の倍率が低くなっています。 +これらのモデルでも、モデルプロバイダーに直接支払う場合より少し多く利用できます。そのため、含まれる月間利用枠が少なくなっています。 --- diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index 5f35bef2c05d..8a18c6665d94 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -121,47 +121,93 @@ OpenCode 외에도 다음 클라이언트가 OpenCode Go에서 올바르게 작 ## 사용 한도 -OpenCode Go에는 다음과 같은 기본 한도가 포함됩니다. +사용 한도는 월간 달러 금액으로 정의됩니다. 아래 표는 각 모델의 월간 한도와 토큰 비용을 보여줍니다. + +각 모델의 사용 한도는 5시간 한도는 월간 한도의 20%, 주간 한도는 50%, 월간 한도는 100%입니다. + +예를 들어 월간 한도가 $60인 모델은 다음 금액까지 사용할 수 있습니다. + +- **5시간 한도** — $12 사용 +- **주간 한도** — $30 사용 +- **월간 한도** — $60 사용 + +토큰 가격은 1M tokens 기준입니다. + +| Model | Input | Output | Cached Read | Cached Write | 월간 한도 | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -- **5시간 한도** — 사용량 $12 -- **주간 한도** — 사용량 $30 -- **월간 한도** — 사용량 $60 +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak 시간은 월요일부터 금요일까지 01:00-04:00 및 06:00-10:00 UTC이며, 주말을 포함한 그 외 모든 시간은 Off-Peak입니다. [자세히 알아보기](https://api-docs.deepseek.com/quick_start/pricing/). -실제 적용되는 할당량은 모델마다 다릅니다. 아래 표를 참조하세요. +**DeepSeek V4 Flash Vision Exp:** 이미지는 크기에 따라 토큰으로 변환되며 텍스트 토큰과 함께 입력 토큰으로 청구됩니다. [자세히 알아보기](https://api-docs.deepseek.com/quick_start/pricing/). -한도는 달러 금액 기준으로 정의됩니다. 즉, 실제 요청 횟수는 사용하는 모델에 따라 달라집니다. MiMo-V2.5처럼 저렴한 모델은 더 많은 요청이 가능하고, GLM-5.2처럼 비용이 더 높은 모델은 더 적은 요청이 가능합니다. +### 예상 요청 횟수 아래 표는 일반적인 Go 사용 패턴을 기준으로 한 예상 요청 횟수를 보여줍니다. | Model | 5시간당 요청 횟수 | 주간 요청 횟수 | 월간 요청 횟수 | -| ---------------------------- | ----------------- | -------------- | -------------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| ---------------------------- | ------------------- | ----------------- | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | 예상치에는 요청당 다음 토큰 수를 사용하며, 실제 사용량은 달라질 수 있습니다. @@ -190,50 +236,6 @@ OpenCode Go에는 다음과 같은 기본 한도가 포함됩니다. - MiMo-V2.5-Pro — 요청당 입력 790, 캐시 86,000, 출력 토큰 305 - Omen Alpha — 요청당 입력 300, 캐시 40,000, 출력 토큰 100 -이 예상치는 또한 1M tokens당 다음 가격과 각 모델에 포함된 월간 사용량을 기준으로 합니다. - -| Model | Input | Output | Cached Read | Cached Write | Usage | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ----- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak 시간은 월요일부터 금요일까지 01:00-04:00 및 06:00-10:00 UTC이며, 주말을 포함한 그 외 모든 시간은 Off-Peak입니다. [자세히 알아보기](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** 이미지는 크기에 따라 토큰으로 변환되며 텍스트 토큰과 함께 입력 토큰으로 청구됩니다. [자세히 알아보기](https://api-docs.deepseek.com/quick_start/pricing/). - 현재 사용량은 **console**에서 확인할 수 있습니다. :::tip @@ -252,13 +254,13 @@ Zen 잔액에 크레딧도 있다면, console에서 **Use balance** 옵션을 ### 일부 모델의 사용량이 더 적은 이유 -Go에서는 월 $10를 지불하며, 대부분의 모델에 대해 그 6배의 사용량을 제공하는 것을 목표로 합니다. +Go에서는 월 $10를 지불하며, 포함된 월간 사용량은 모델마다 다릅니다. -대부분의 모델은 대량 할인과 예약된 GPU 용량을 통해 이를 실현합니다. 그런 다음 6배의 사용량 배율을 통해 절감 혜택을 사용자에게 돌려드립니다. +대부분의 모델은 대량 할인과 예약된 GPU 용량을 통해 이를 실현합니다. 그런 다음 더 많은 월간 사용량을 제공하는 방식으로 절감 혜택을 사용자에게 돌려드립니다. 일부 모델은 새로 출시되었거나 공개 가격에 이미 할인이 적용되어 있어, 아직 할인을 협상하거나 더 저렴한 비용으로 호스팅할 기회가 없었습니다. -이러한 모델도 모델 제공자에게 직접 비용을 지불할 때보다 약간 더 많은 사용량을 제공합니다. 이것이 위 표에서 해당 모델의 사용량 배율이 더 낮은 이유입니다. +이러한 모델도 모델 제공자에게 직접 비용을 지불할 때보다 약간 더 많은 사용량을 제공합니다. 이것이 해당 모델에 포함된 월간 사용량이 더 적은 이유입니다. --- diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index 44c019f31987..ed4f636257c9 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -134,47 +134,93 @@ undersøkte. De lenkede rapportene følger rettelser og midlertidige løsninger. ## Bruksgrenser -OpenCode Go inkluderer følgende basisgrenser: +Bruksgrenser defineres som månedlige dollarbeløp. Tabellen nedenfor viser den månedlige grensen og tokenkostnadene for hver modell. + +Hver modell har følgende bruksgrenser: 5-timersgrense — 20 % av den månedlige grensen; ukentlig grense — 50 %; og månedlig grense — 100 %. + +Hvis en modell for eksempel har en månedlig grense på $60, kan du bruke opptil: - **5-timers grense** — $12 i bruk - **Ukentlig grense** — $30 i bruk - **Månedlig grense** — $60 i bruk -Den effektive bruken varierer etter modell; se tabellen nedenfor. +Tokenpriser er oppgitt per 1M tokens. + +| Model | Input | Output | Cached Read | Cached Write | Månedlig grense | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | + +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak-tidene er 01:00-04:00 og 06:00-10:00 UTC fra mandag til fredag; alle andre tider, inkludert helger, er Off-Peak. [Les mer](https://api-docs.deepseek.com/quick_start/pricing/). + +**DeepSeek V4 Flash Vision Exp:** Bilder konverteres til tokens basert på dimensjonene og faktureres som input-tokens sammen med tekst-tokens. [Les mer](https://api-docs.deepseek.com/quick_start/pricing/). -Grensene er definert i dollarverdi. Dette betyr at ditt faktiske antall forespørsler avhenger av modellen du bruker. Billigere modeller som MiMo-V2.5 tillater flere forespørsler, mens dyrere modeller som GLM-5.2 tillater færre. +### Estimerte forespørsler -Tabellen nedenfor gir et estimert antall forespørsler basert på typiske bruksmønstre for Go: +Tabellen nedenfor viser 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 | -| ---------------------------- | ------------------------ | -------------------- | ---------------------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| ---------------------------- | ------------------- | ----------------- | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Estimatene bruker følgende antall tokens per forespørsel; faktisk bruk varierer. @@ -203,50 +249,6 @@ Estimatene bruker følgende antall tokens per forespørsel; faktisk bruk variere - MiMo-V2.5-Pro — 790 input, 86 000 bufret, 305 output-tokens per forespørsel - Omen Alpha — 300 input-, 40 000 bufrede, 100 output-tokens per forespørsel -Estimatene er også basert på følgende priser per 1M tokens og den månedlige bruken som er inkludert med hver modell: - -| Model | Input | Output | Cached Read | Cached Write | Bruk | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ---- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak-tidene er 01:00-04:00 og 06:00-10:00 UTC fra mandag til fredag; alle andre tider, inkludert helger, er Off-Peak. [Les mer](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** Bilder konverteres til tokens basert på dimensjonene og faktureres som input-tokens sammen med tekst-tokens. [Les mer](https://api-docs.deepseek.com/quick_start/pricing/). - Du kan spore din nåværende bruk i **konsollen**. :::tip @@ -267,13 +269,13 @@ etter at du har nådd bruksgrensene dine, i stedet for å blokkere forespørsler ### Hvorfor noen modeller har lavere bruk -Med Go betaler du $10/måned, og for de fleste modeller har vi som mål å gi deg seks ganger så mye bruk. +Med Go betaler du $10/måned, og den inkluderte månedlige bruken varierer etter modell. -For de fleste modeller får vi dette til gjennom volumrabatter og reservert GPU-kapasitet. Deretter gir vi disse besparelsene videre til deg gjennom 6x-multiplikatoren. +For de fleste modeller får vi dette til gjennom volumrabatter og reservert GPU-kapasitet. Deretter gir vi disse besparelsene videre til deg i form av mer månedlig bruk. For noen modeller har vi ikke hatt muligheten til å forhandle frem en rabatt eller drifte dem til en lavere kostnad, enten fordi modellen er ny, eller fordi den offentlige prisen allerede er rabattert. -For disse modellene får du fortsatt litt mer enn om du betalte modellleverandørene direkte. Derfor er bruksmultiplikatoren deres lavere i tabellen ovenfor. +For disse modellene får du fortsatt litt mer enn om du betalte modellleverandørene direkte. Derfor er den inkluderte månedlige bruken deres lavere. --- diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index 3e8213b8aeec..5c60f7772368 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -128,47 +128,93 @@ Raporty pod podanymi linkami służą do śledzenia poprawek i obejść. ## Limity użycia -OpenCode Go zawiera następujące limity bazowe: +Limity użycia są określane jako miesięczne kwoty w dolarach. Poniższa tabela przedstawia miesięczny limit i koszty tokenów dla każdego modelu. + +Każdy model ma następujące limity użycia: 5-godzinny — 20% miesięcznego limitu; tygodniowy — 50%; miesięczny — 100%. + +Na przykład, jeśli model ma miesięczny limit $60, możesz wykorzystać do: + +- **Limit 5-godzinny** — użycie o wartości $12 +- **Limit tygodniowy** — użycie o wartości $30 +- **Limit miesięczny** — użycie o wartości $60 + +Ceny tokenów podano za 1M tokenów. + +| Model | Wejście | Wyjście | Odczyt z cache | Zapis do cache | Limit miesięczny | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -- **Limit 5-godzinny** — użycie o wartości 12 $ -- **Limit tygodniowy** — użycie o wartości 30 $ -- **Limit miesięczny** — użycie o wartości 60 $ +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Godziny Peak to 01:00-04:00 i 06:00-10:00 UTC od poniedziałku do piątku; wszystkie pozostałe godziny, w tym weekendy, to Off-Peak. [Dowiedz się więcej](https://api-docs.deepseek.com/quick_start/pricing/). -Efektywny limit różni się w zależności od modelu; zobacz tabelę poniżej. +**DeepSeek V4 Flash Vision Exp:** Obrazy są przeliczane na tokeny na podstawie ich wymiarów i rozliczane jako tokeny wejściowe razem z tokenami tekstowymi. [Dowiedz się więcej](https://api-docs.deepseek.com/quick_start/pricing/). -Limity są zdefiniowane w wartości w dolarach. Oznacza to, że rzeczywista liczba żądań zależy od używanego modelu. Tańsze modele, takie jak MiMo-V2.5, pozwalają na więcej żądań, podczas gdy modele o wyższym koszcie, takie jak GLM-5.2, pozwalają na mniej. +### Szacunkowa liczba żądań 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 | -| ---------------------------- | ------------------- | ------------------ | ------------------ | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| ---------------------------- | ------------------- | ----------------- | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Szacunki wykorzystują następującą liczbę tokenów na żądanie; rzeczywiste użycie jest zmienne. @@ -197,50 +243,6 @@ Szacunki wykorzystują następującą liczbę tokenów na żądanie; rzeczywiste - MiMo-V2.5-Pro — 790 tokenów wejściowych, 86 000 w pamięci podręcznej, 305 tokenów wyjściowych na żądanie - Omen Alpha — 300 tokenów wejściowych, 40 000 w pamięci podręcznej, 100 tokenów wyjściowych na żądanie -Szacunki opierają się również na następujących cenach za 1M tokenów oraz miesięcznym użyciu dostępnym dla każdego modelu: - -| Model | Wejście | Wyjście | Odczyt z cache | Zapis do cache | Użycie | -| --------------------------------------- | ------- | ------- | -------------- | -------------- | ------ | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Godziny Peak to 01:00-04:00 i 06:00-10:00 UTC od poniedziałku do piątku; wszystkie pozostałe godziny, w tym weekendy, to Off-Peak. [Dowiedz się więcej](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** Obrazy są przeliczane na tokeny na podstawie ich wymiarów i rozliczane jako tokeny wejściowe razem z tokenami tekstowymi. [Dowiedz się więcej](https://api-docs.deepseek.com/quick_start/pricing/). - Możesz śledzić swoje bieżące zużycie w **konsoli**. :::tip @@ -259,13 +261,13 @@ Jeśli masz również środki na swoim saldzie Zen, możesz włączyć opcję ** ### Dlaczego niektóre modele mają niższe limity użycia -Go kosztuje $10/miesiąc, a w przypadku większości modeli naszym celem jest zapewnienie Ci użycia o wartości 6x większej niż ta kwota. +Go kosztuje $10/miesiąc, a miesięczne użycie w ramach subskrypcji różni się w zależności od modelu. -W przypadku większości modeli jest to możliwe dzięki rabatom hurtowym i zarezerwowanej mocy obliczeniowej GPU. Uzyskane w ten sposób oszczędności przekazujemy Tobie w postaci mnożnika 6x. +W przypadku większości modeli jest to możliwe dzięki rabatom hurtowym i zarezerwowanej mocy obliczeniowej GPU. Uzyskane w ten sposób oszczędności przekazujemy Tobie w postaci większego miesięcznego użycia. W przypadku niektórych modeli nie mieliśmy jeszcze możliwości wynegocjowania rabatu ani hostowania ich po niższym koszcie, ponieważ model jest nowy lub jego publiczny cennik już uwzględnia rabat. -W przypadku tych modeli nadal otrzymujesz nieco więcej, niż płacąc bezpośrednio ich dostawcom. Dlatego ich mnożnik użycia w powyższej tabeli jest niższy. +W przypadku tych modeli nadal otrzymujesz nieco więcej, niż płacąc bezpośrednio ich dostawcom. Dlatego ich miesięczne użycie w ramach subskrypcji jest niższe. --- diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index 98cef1325a66..ab40591b53e6 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -134,22 +134,67 @@ investigamos. Os relatórios vinculados acompanham correções e soluções alte ## Limites de uso -O OpenCode Go inclui os seguintes limites base: +Os limites de uso são definidos como valores mensais em dólares. A tabela abaixo mostra o limite mensal e os custos de tokens de cada modelo. -- **Limite de 5 horas** — US$ 12 de uso -- **Limite semanal** — US$ 30 de uso -- **Limite mensal** — US$ 60 de uso +Cada modelo tem os seguintes limites de uso: 5 horas — 20% do limite mensal; semanal — 50%; e mensal — 100%. -A cota efetiva varia conforme o modelo; consulte a tabela abaixo. +Por exemplo, se um modelo tiver um limite mensal de $60, você poderá gastar até: -Os limites são definidos em valor em dólares. Isso significa que a sua contagem real de requisições depende do modelo que você usa. Modelos mais baratos como o MiMo-V2.5 permitem mais requisições, enquanto modelos de custo mais alto como o GLM-5.2 permitem menos. +- **Limite de 5 horas** — $12 de uso +- **Limite semanal** — $30 de uso +- **Limite mensal** — $60 de uso -A tabela abaixo fornece uma contagem estimada de requisições com base nos padrões típicos de uso do Go: +Os preços de tokens são indicados por 1M tokens. + +| Modelo | Entrada | Saída | Leitura em cache | Escrita em cache | Limite mensal | +| --------------------------------------- | ------- | ------ | ---------------- | ---------------- | ---- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | + +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Os horários Peak são 01:00-04:00 e 06:00-10:00 UTC, de segunda a sexta-feira; todos os demais horários, incluindo os fins de semana, são Off-Peak. [Saiba mais](https://api-docs.deepseek.com/quick_start/pricing/). + +**DeepSeek V4 Flash Vision Exp:** As imagens são convertidas em tokens com base em suas dimensões e cobradas como tokens de entrada junto com os tokens de texto. [Saiba mais](https://api-docs.deepseek.com/quick_start/pricing/). + +### Requisições estimadas + +A tabela abaixo apresenta uma quantidade 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 | | ---------------------------- | ----------------------- | ---------------------- | ------------------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| Omen Alpha | 11,600 | 29,000 | 57,900 | | GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | @@ -174,7 +219,8 @@ A tabela abaixo fornece uma contagem estimada de requisições com base nos padr | DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | | Hy4 preview | 1,350 | 3,380 | 6,770 | | Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | As estimativas usam as seguintes quantidades de tokens por requisição; o uso real varia. @@ -203,50 +249,6 @@ As estimativas usam as seguintes quantidades de tokens por requisição; o uso r - MiMo-V2.5-Pro — 790 tokens de entrada, 86.000 em cache, 305 tokens de saída por requisição - Omen Alpha — 300 tokens de entrada, 40.000 em cache, 100 tokens de saída por requisição -As estimativas também se baseiam nos seguintes preços por 1M tokens e no uso mensal incluído com cada modelo: - -| Modelo | Entrada | Saída | Leitura em cache | Escrita em cache | Uso | -| --------------------------------------- | ------- | ------ | ---------------- | ---------------- | ---- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Os horários Peak são 01:00-04:00 e 06:00-10:00 UTC, de segunda a sexta-feira; todos os demais horários, incluindo os fins de semana, são Off-Peak. [Saiba mais](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** As imagens são convertidas em tokens com base em suas dimensões e cobradas como tokens de entrada junto com os tokens de texto. [Saiba mais](https://api-docs.deepseek.com/quick_start/pricing/). - Você pode acompanhar o seu uso atual no **console**. :::tip @@ -267,13 +269,13 @@ após você atingir os seus limites de uso em vez de bloquear as requisições. ### Por que alguns modelos têm um uso menor -Com o Go, você paga $10/mês e, para a maioria dos modelos, nosso objetivo é oferecer 6x esse valor em uso. +Com o Go, você paga $10/mês, e o uso mensal incluído varia conforme o modelo. -Para a maioria dos modelos, conseguimos fazer isso por meio de descontos por volume e capacidade reservada de GPU. Repassamos essa economia a você por meio do multiplicador de 6x. +Para a maioria dos modelos, conseguimos fazer isso por meio de descontos por volume e capacidade reservada de GPU. Repassamos essa economia a você na forma de um uso mensal maior. Para alguns modelos, ainda não tivemos a oportunidade de negociar um desconto ou hospedá-los a um custo menor, seja porque o modelo é novo ou porque o preço público já tem desconto. -Para esses modelos, você ainda recebe um pouco mais do que receberia se pagasse diretamente aos provedores dos modelos; por isso, o multiplicador de uso deles é menor na tabela acima. +Para esses modelos, você ainda recebe um pouco mais do que receberia se pagasse diretamente aos provedores dos modelos; por isso, o uso mensal incluído com eles é menor. --- diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index 0e8842d1484a..f9a4996fc916 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -134,47 +134,93 @@ OpenCode Go предназначен для [OpenCode](https://opencode.ai) и ## Лимиты использования -OpenCode Go включает следующие базовые лимиты: +Лимиты использования определяются как месячные суммы в долларах. В таблице ниже указаны месячный лимит и стоимость токенов для каждой модели. -- **Лимит на 5 часов** — $12 использования -- **Недельный лимит** — $30 использования -- **Месячный лимит** — $60 использования +Для каждой модели действуют следующие лимиты использования: на 5 часов — 20% месячного лимита; на неделю — 50%; на месяц — 100%. -Эффективный лимит зависит от модели; см. таблицу ниже. +Например, если месячный лимит модели составляет $60, вы можете потратить до: -Лимиты определены в долларовом эквиваленте. Это означает, что ваше фактическое количество запросов зависит от используемой модели. Более дешевые модели, такие как MiMo-V2.5, позволяют делать больше запросов, в то время как более дорогие, такие как GLM-5.2, — меньше. +- **Лимит на 5 часов** — использование на сумму $12 +- **Недельный лимит** — использование на сумму $30 +- **Месячный лимит** — использование на сумму $60 -В таблице ниже приведено примерное количество запросов на основе типичных сценариев использования Go: +Цены на токены указаны за 1M токенов. + +| Model | Input | Output | Cached Read | Cached Write | Месячный лимит | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | + +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Часы Peak с понедельника по пятницу: 01:00-04:00 и 06:00-10:00 UTC; все остальные часы, включая выходные, относятся к Off-Peak. [Подробнее](https://api-docs.deepseek.com/quick_start/pricing/). + +**DeepSeek V4 Flash Vision Exp:** Изображения преобразуются в токены с учётом их размеров и оплачиваются как входные токены вместе с текстовыми токенами. [Подробнее](https://api-docs.deepseek.com/quick_start/pricing/). + +### Примерное число запросов + +В таблице ниже приведено примерное число запросов на основе типичных сценариев использования Go: | Model | запросов за 5 часов | запросов в неделю | запросов в месяц | -| ---------------------------- | ------------------- | ----------------- | ---------------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| ---------------------------- | ------------------- | ----------------- | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | В оценках используются следующие количества токенов на запрос; фактическое использование может отличаться. @@ -203,50 +249,6 @@ OpenCode Go включает следующие базовые лимиты: - MiMo-V2.5-Pro — 790 входных, 86,000 кешированных, 305 выходных токенов на запрос - Omen Alpha — 300 входных, 40 000 кешированных, 100 выходных токенов на запрос -Эти оценки также основаны на следующих ценах за 1M токенов и месячном объеме использования, включенном для каждой модели: - -| Model | Input | Output | Cached Read | Cached Write | Использование | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Часы Peak с понедельника по пятницу: 01:00-04:00 и 06:00-10:00 UTC; все остальные часы, включая выходные, относятся к Off-Peak. [Подробнее](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** Изображения преобразуются в токены с учётом их размеров и оплачиваются как входные токены вместе с текстовыми токенами. [Подробнее](https://api-docs.deepseek.com/quick_start/pricing/). - Вы можете отслеживать текущее использование в **консоли**. :::tip @@ -267,13 +269,13 @@ OpenCode Go включает следующие базовые лимиты: ### Почему для некоторых моделей доступен меньший объем использования -С Go вы платите $10 в месяц, а для большинства моделей мы стремимся предоставить вам объем использования стоимостью в шесть раз больше этой суммы. +С Go вы платите $10 в месяц, а включённый объём использования за месяц зависит от модели. -Для большинства моделей это возможно благодаря оптовым скидкам и зарезервированным мощностям GPU. Полученную экономию мы передаем вам за счет шестикратного множителя. +Для большинства моделей это возможно благодаря оптовым скидкам и зарезервированным мощностям GPU. Полученную экономию мы передаем вам в виде большего объёма использования за месяц. Для некоторых моделей у нас еще не было возможности договориться о скидке или развернуть их с меньшими затратами: либо модель новая, либо ее публичные тарифы уже включают скидку. -Для этих моделей вы все равно получаете немного больше, чем при прямой оплате их провайдерам; именно поэтому указанный для них в таблице выше множитель использования ниже. +Для этих моделей вы все равно получаете немного больше, чем при прямой оплате их провайдерам; именно поэтому включённый для них объём использования за месяц меньше. --- diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index dfaa30d69e0b..7246269cbc5a 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -121,47 +121,93 @@ OpenCode Go ออกแบบมาสำหรับ [OpenCode](https://openco ## Usage limits -OpenCode Go มีขีดจำกัดพื้นฐานดังต่อไปนี้: +ขีดจำกัดการใช้งานกำหนดเป็นจำนวนเงินดอลลาร์รายเดือน ตารางด้านล่างแสดงขีดจำกัดรายเดือนและค่า token ของแต่ละโมเดล + +แต่ละโมเดลมีขีดจำกัดการใช้งานดังนี้: 5 ชั่วโมง — 20% ของขีดจำกัดรายเดือน; รายสัปดาห์ — 50%; และรายเดือน — 100% + +ตัวอย่างเช่น หากโมเดลมีขีดจำกัดรายเดือน $60 คุณสามารถใช้งานได้สูงสุด: + +- **ขีดจำกัดต่อ 5 ชั่วโมง** — การใช้งาน $12 +- **ขีดจำกัดรายสัปดาห์** — การใช้งาน $30 +- **ขีดจำกัดรายเดือน** — การใช้งาน $60 + +ราคา token แสดงต่อ 1M tokens + +| Model | Input | Output | Cached Read | Cached Write | ขีดจำกัดรายเดือน | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -- **ขีดจำกัดต่อ 5 ชั่วโมง** — การใช้งานมูลค่า $12 -- **ขีดจำกัดรายสัปดาห์** — การใช้งานมูลค่า $30 -- **ขีดจำกัดรายเดือน** — การใช้งานมูลค่า $60 +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** ช่วงเวลา Peak คือ 01:00-04:00 และ 06:00-10:00 UTC ตั้งแต่วันจันทร์ถึงวันศุกร์ ส่วนเวลาอื่นทั้งหมด รวมถึงวันหยุดสุดสัปดาห์ เป็น Off-Peak [ดูข้อมูลเพิ่มเติม](https://api-docs.deepseek.com/quick_start/pricing/) -ขีดจำกัดการใช้งานที่มีผลแตกต่างกันไปตามโมเดล โปรดดูตารางด้านล่าง +**DeepSeek V4 Flash Vision Exp:** รูปภาพจะถูกแปลงเป็น token ตามขนาด และคิดค่าบริการเป็น input token รวมกับ text token [ดูข้อมูลเพิ่มเติม](https://api-docs.deepseek.com/quick_start/pricing/) -ขีดจำกัดถูกกำหนดเป็นมูลค่าดอลลาร์ ซึ่งหมายความว่าจำนวน request จริงของคุณจะขึ้นอยู่กับโมเดลที่คุณใช้งาน โมเดลที่ราคาถูกกว่าอย่าง MiMo-V2.5 จะสามารถส่ง request ได้มากกว่า ในขณะที่โมเดลที่มีราคาสูงกว่าอย่าง GLM-5.2 จะส่งได้น้อยกว่า +### requests โดยประมาณ ตารางด้านล่างแสดงจำนวน request โดยประมาณตามรูปแบบการใช้งานปกติของ Go: | Model | requests ต่อ 5 ชั่วโมง | requests ต่อสัปดาห์ | requests ต่อเดือน | -| ---------------------------- | ---------------------- | ------------------- | ----------------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| ---------------------------- | ------------------- | ----------------- | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | การประมาณการใช้จำนวน token ต่อ request ดังต่อไปนี้ การใช้งานจริงอาจแตกต่างกัน @@ -190,50 +236,6 @@ OpenCode Go มีขีดจำกัดพื้นฐานดังต่ - MiMo-V2.5-Pro — 790 input, 86,000 cached, 305 output tokens ต่อ request - Omen Alpha — 300 input, 40,000 cached, 100 output tokens ต่อ request -การประมาณการนี้ยังอ้างอิงจากราคาต่อ 1M tokens และปริมาณการใช้งานรายเดือนที่รวมอยู่ในแต่ละโมเดลดังต่อไปนี้: - -| Model | Input | Output | Cached Read | Cached Write | Usage | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ----- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** ช่วงเวลา Peak คือ 01:00-04:00 และ 06:00-10:00 UTC ตั้งแต่วันจันทร์ถึงวันศุกร์ ส่วนเวลาอื่นทั้งหมด รวมถึงวันหยุดสุดสัปดาห์ เป็น Off-Peak [ดูข้อมูลเพิ่มเติม](https://api-docs.deepseek.com/quick_start/pricing/) - -**DeepSeek V4 Flash Vision Exp:** รูปภาพจะถูกแปลงเป็น token ตามขนาด และคิดค่าบริการเป็น input token รวมกับ text token [ดูข้อมูลเพิ่มเติม](https://api-docs.deepseek.com/quick_start/pricing/) - คุณสามารถติดตามการใช้งานปัจจุบันของคุณได้ใน **console** :::tip @@ -252,13 +254,13 @@ OpenCode Go มีขีดจำกัดพื้นฐานดังต่ ### เหตุใดบางโมเดลจึงมีปริมาณการใช้งานต่ำกว่า -สำหรับ Go คุณจ่าย $10/เดือน และสำหรับโมเดลส่วนใหญ่ เราตั้งเป้าที่จะมอบปริมาณการใช้งานให้คุณ 6 เท่าของจำนวนดังกล่าว +สำหรับ Go คุณจ่าย $10/เดือน และปริมาณการใช้งานรายเดือนที่รวมอยู่จะแตกต่างกันตามโมเดล -สำหรับโมเดลส่วนใหญ่ เราทำเช่นนี้ได้ผ่านส่วนลดสำหรับการซื้อจำนวนมากและความจุ GPU ที่จองไว้ จากนั้นเราจะส่งต่อส่วนลดเหล่านั้นให้คุณผ่านตัวคูณ 6 เท่า +สำหรับโมเดลส่วนใหญ่ เราทำเช่นนี้ได้ผ่านส่วนลดสำหรับการซื้อจำนวนมากและความจุ GPU ที่จองไว้ จากนั้นเราจะส่งต่อส่วนลดเหล่านั้นให้คุณในรูปของปริมาณการใช้งานรายเดือนที่มากขึ้น สำหรับบางโมเดล เรายังไม่มีโอกาสเจรจาส่วนลดหรือโฮสต์โมเดลเหล่านั้นด้วยต้นทุนที่ต่ำลง ไม่ว่าจะเป็นเพราะโมเดลนั้นใหม่หรือราคาที่ประกาศต่อสาธารณะมีส่วนลดอยู่แล้ว -สำหรับโมเดลเหล่านี้ คุณยังคงได้รับปริมาณการใช้งานมากกว่าการจ่ายให้ผู้ให้บริการโมเดลโดยตรงเล็กน้อย นี่คือเหตุผลที่ตัวคูณการใช้งานของโมเดลเหล่านี้ต่ำกว่าในตารางด้านบน +สำหรับโมเดลเหล่านี้ คุณยังคงได้รับปริมาณการใช้งานมากกว่าการจ่ายให้ผู้ให้บริการโมเดลโดยตรงเล็กน้อย นี่คือเหตุผลที่ปริมาณการใช้งานรายเดือนที่รวมอยู่ของโมเดลเหล่านี้ต่ำกว่า --- diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index 1b6a41b45325..fb21b09fc002 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -121,47 +121,93 @@ Bağlantı verilen bildirimlerden düzeltmeleri ve geçici çözümleri takip ed ## Kullanım limitleri -OpenCode Go aşağıdaki temel limitleri içerir: +Kullanım limitleri aylık dolar tutarları olarak tanımlanır. Aşağıdaki tabloda her modelin aylık limiti ve token maliyetleri gösterilmektedir. + +Her model için şu kullanım limitleri geçerlidir: 5 saatlik — aylık limitin %20'si; haftalık — %50'si; aylık — %100'ü. + +Örneğin bir modelin aylık limiti $60 ise şu tutarlara kadar kullanım yapabilirsiniz: + +- **5 saatlik limit** — $12 tutarında kullanım +- **Haftalık limit** — $30 tutarında kullanım +- **Aylık limit** — $60 tutarında kullanım + +Token fiyatları 1M token başına verilmiştir. + +| Model | Input | Output | Cached Read | Cached Write | Aylık limit | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | -- **5 saatlik limit** — 12$ kullanım -- **Haftalık limit** — 30$ kullanım -- **Aylık limit** — 60$ kullanım +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak saatleri pazartesiden cumaya 01:00-04:00 ve 06:00-10:00 UTC'dir; hafta sonları dahil diğer tüm saatler Off-Peak'tir. [Daha fazla bilgi](https://api-docs.deepseek.com/quick_start/pricing/). -Etkin kullanım limiti modele göre değişir; aşağıdaki tabloya bakın. +**DeepSeek V4 Flash Vision Exp:** Görseller boyutlarına göre token'lara dönüştürülür ve metin token'larıyla birlikte girdi token'ları olarak ücretlendirilir. [Daha fazla bilgi](https://api-docs.deepseek.com/quick_start/pricing/). -Limitler dolar değeri üzerinden belirlenmiştir. Bu, gerçek istek sayınızın kullandığınız modele bağlı olduğu anlamına gelir. MiMo-V2.5 gibi daha ucuz modeller daha fazla isteğe izin verirken, GLM-5.2 gibi yüksek maliyetli modeller daha azına izin verir. +### Tahmini istek sayısı -Aşağıdaki tablo, tipik Go kullanım modellerine dayalı tahmini bir istek sayısı sunmaktadır: +Aşağıdaki tabloda tipik Go kullanım modellerine dayalı tahmini istek sayısı verilmiştir: | Model | 5 saatte bir istek | haftalık istek | aylık istek | -| ---------------------------- | ------------------ | -------------- | ----------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| ---------------------------- | ------------------- | ----------------- | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Tahminler istek başına aşağıdaki token sayılarını kullanır; gerçek kullanım değişir. @@ -190,50 +236,6 @@ Tahminler istek başına aşağıdaki token sayılarını kullanır; gerçek kul - MiMo-V2.5-Pro — İstek başına 790 girdi, 86.000 önbelleğe alınmış, 305 çıktı token'ı - Omen Alpha — İstek başına 300 girdi, 40.000 önbelleğe alınmış, 100 çıktı token'ı -Tahminler ayrıca 1M token başına aşağıdaki fiyatlara ve her modelle birlikte sunulan aylık kullanıma dayanır: - -| Model | Input | Output | Cached Read | Cached Write | Kullanım | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | -------- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak saatleri pazartesiden cumaya 01:00-04:00 ve 06:00-10:00 UTC'dir; hafta sonları dahil diğer tüm saatler Off-Peak'tir. [Daha fazla bilgi](https://api-docs.deepseek.com/quick_start/pricing/). - -**DeepSeek V4 Flash Vision Exp:** Görseller boyutlarına göre token'lara dönüştürülür ve metin token'larıyla birlikte girdi token'ları olarak ücretlendirilir. [Daha fazla bilgi](https://api-docs.deepseek.com/quick_start/pricing/). - Mevcut kullanımınızı **konsoldan** takip edebilirsiniz. :::tip @@ -252,13 +254,13 @@ Eğer Zen bakiyenizde kredileriniz varsa, konsoldan **Bakiye kullan (Use balance ### Bazı modellerin kullanımı neden daha düşük? -Go ile aylık 10$ ödersiniz ve çoğu model için size bunun 6 katı değerinde kullanım sunmayı hedefleriz. +Go ile aylık 10$ ödersiniz ve sunulan aylık kullanım modele göre değişir. -Çoğu modelde bunu toplu indirimler ve ayrılmış GPU kapasitesi sayesinde mümkün kılıyoruz. Ardından bu tasarrufları 6 katlık çarpanla size aktarıyoruz. +Çoğu modelde bunu toplu indirimler ve ayrılmış GPU kapasitesi sayesinde mümkün kılıyoruz. Ardından bu tasarrufları daha fazla aylık kullanım olarak size aktarıyoruz. Bazı modellerde ise model yeni olduğu veya herkese açık fiyatlandırması zaten indirimli olduğu için henüz indirim pazarlığı yapma ya da modeli daha düşük maliyetle barındırma fırsatımız olmadı. -Bu modellerde bile model sağlayıcılarına doğrudan ödeme yaptığınız duruma kıyasla biraz daha fazla kullanım elde edersiniz; bu nedenle yukarıdaki tabloda kullanım çarpanları daha düşüktür. +Bu modellerde bile model sağlayıcılarına doğrudan ödeme yaptığınız duruma kıyasla biraz daha fazla kullanım elde edersiniz; bu nedenle sunulan aylık kullanım daha azdır. --- diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index 539fa1328743..ad4ffe237d5c 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -120,22 +120,67 @@ OpenCode Go 适用于 [OpenCode](https://opencode.ai) 以及其他会产生类 ## 使用限制 -OpenCode Go 包含以下基础限制: +使用限制以每月美元金额定义。下表列出了每个模型的每月限制和 token 成本。 -- **5 小时限制** — 12 美元使用额度 -- **每周限制** — 30 美元使用额度 -- **每月限制** — 60 美元使用额度 +每个模型都有以下使用限制:5 小时 — 每月限制的 20%;每周 — 50%;每月 — 100%。 -有效使用额度因模型而异;请参见下表。 +例如,如果某个模型的每月限制为 $60,你最多可以使用: -限制以美元价值定义。这意味着你的实际请求数取决于你所使用的模型。较便宜的模型(如 MiMo-V2.5)允许更多请求,而较高成本的模型(如 GLM-5.2)允许较少请求。 +- **5 小时限制** — $12 的使用额度 +- **每周限制** — $30 的使用额度 +- **每月限制** — $60 的使用额度 -下表提供了基于典型 Go 使用模式的预估请求数: +Token 价格按每 1M tokens 列示。 + +| 模型 | 输入 | 输出 | 缓存读取 | 缓存写入 | 每月限制 | +| --------------------------------------- | ------ | ------ | --------- | -------- | -------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | + +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak 时段为周一至周五的 01:00-04:00 和 06:00-10:00 UTC;其他所有时段(包括周末)均为 Off-Peak。[了解更多](https://api-docs.deepseek.com/quick_start/pricing/)。 + +**DeepSeek V4 Flash Vision Exp:** 图片会根据尺寸转换为 token,并与文本 token 一起按输入 token 计费。 [了解更多](https://api-docs.deepseek.com/quick_start/pricing/)。 + +### 预估请求数 + +下表根据典型的 Go 使用模式提供了预估请求数: | Model | 每 5 小时请求数 | 每周请求数 | 每月请求数 | | ---------------------------- | --------------- | ---------- | ---------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| Omen Alpha | 11,600 | 29,000 | 57,900 | | GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | @@ -160,7 +205,8 @@ OpenCode Go 包含以下基础限制: | DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | | Hy4 preview | 1,350 | 3,380 | 6,770 | | Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | 预估值采用以下每次请求的 token 数量;实际使用情况会有所不同。 @@ -189,50 +235,6 @@ OpenCode Go 包含以下基础限制: - Hy3 — 每次请求 830 个输入 token,71,500 个缓存 token,295 个输出 token - Omen Alpha — 每次请求 300 个输入 token,40,000 个缓存 token,100 个输出 token -预估值还基于以下每 1M tokens 的价格以及每个模型包含的每月使用额度: - -| 模型 | 输入 | 输出 | 缓存读取 | 缓存写入 | 使用额度 | -| --------------------------------------- | ------ | ------ | --------- | -------- | -------- | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak 时段为周一至周五的 01:00-04:00 和 06:00-10:00 UTC;其他所有时段(包括周末)均为 Off-Peak。[了解更多](https://api-docs.deepseek.com/quick_start/pricing/)。 - -**DeepSeek V4 Flash Vision Exp:** 图片会根据尺寸转换为 token,并与文本 token 一起按输入 token 计费。 [了解更多](https://api-docs.deepseek.com/quick_start/pricing/)。 - 你可以在 **控制台** 中跟踪你当前的使用情况。 :::tip @@ -251,13 +253,13 @@ OpenCode Go 包含以下基础限制: ### 为什么某些模型的使用额度较低 -使用 Go 时,你每月支付 $10;对于大多数模型,我们的目标是提供 6 倍于此的使用额度。 +使用 Go 时,你每月支付 $10,包含的每月使用额度因模型而异。 -对于大多数模型,我们通过批量折扣和预留 GPU 容量来实现这一目标。然后,我们通过 6 倍乘数将这些节省的成本回馈给你。 +对于大多数模型,我们通过批量折扣和预留 GPU 容量来实现这一目标。然后,我们通过提高每月使用额度,将节省的成本回馈给你。 对于某些模型,我们还没有机会协商折扣或以更低的成本托管它们,这可能是因为模型较新,或者其公开价格已经是折扣价。 -对于这些模型,你获得的使用额度仍会略高于直接向模型提供商付费;这就是它们在上表中的使用额度乘数较低的原因。 +对于这些模型,你获得的使用额度仍会略高于直接向模型提供商付费;这就是它们包含的每月使用额度较低的原因。 --- diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index cd5e690af914..349336f8ff3a 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -120,22 +120,67 @@ OpenCode Go 適用於 [OpenCode](https://opencode.ai) 以及其他會產生類 ## 使用限制 -OpenCode Go 包含以下基準限制: +使用限制以每月美元金額定義。下表列出每個模型的每月限制和 token 成本。 -- **5 小時限制** — $12 美元的使用量 -- **每週限制** — $30 美元的使用量 -- **每月限制** — $60 美元的使用量 +每個模型都有以下使用限制:5 小時 — 每月限制的 20%;每週 — 50%;每月 — 100%。 -有效使用額度因模型而異;請參閱下表。 +例如,如果某個模型的每月限制為 $60,您最多可以使用: -限制是以美元價值來定義。這意味著您的實際請求次數取決於您使用的模型。像 MiMo-V2.5 這樣較便宜的模型允許更多的請求次數,而像 GLM-5.2 這樣成本較高的模型則允許較少次數。 +- **5 小時限制** — $12 的使用額度 +- **每週限制** — $30 的使用額度 +- **每月限制** — $60 的使用額度 -下表提供了基於典型 Go 使用模式的預估請求次數: +Token 價格按每 1M tokens 列示。 + +| 模型 | 輸入 | 輸出 | 快取讀取 | 快取寫入 | 每月限制 | +| --------------------------------------- | ------ | ------ | --------- | -------- | ------ | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | + +**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak 時段為週一至週五的 01:00-04:00 和 06:00-10:00 UTC;其他所有時段(包括週末)均為 Off-Peak。[了解更多](https://api-docs.deepseek.com/quick_start/pricing/)。 + +**DeepSeek V4 Flash Vision Exp:** 圖片會根據尺寸轉換為 token,並與文字 token 一起按輸入 token 計費。 [了解更多](https://api-docs.deepseek.com/quick_start/pricing/)。 + +### 預估請求次數 + +下表根據典型的 Go 使用模式提供預估請求次數: | Model | 每 5 小時請求數 | 每週請求數 | 每月請求數 | | ---------------------------- | --------------- | ---------- | ---------- | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| Omen Alpha | 11,600 | 29,000 | 57,900 | | GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | | GLM-5.3 | 220 | 540 | 1,080 | | GLM-5.2 | 880 | 2,150 | 4,300 | @@ -160,7 +205,8 @@ OpenCode Go 包含以下基準限制: | DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | | Hy4 preview | 1,350 | 3,380 | 6,770 | | Hy3 | 4,300 | 10,750 | 21,500 | -| Omen Alpha | 11,600 | 29,000 | 57,900 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | 這些預估值採用以下每次請求的 token 數量;實際使用情況會有所不同。 @@ -189,50 +235,6 @@ OpenCode Go 包含以下基準限制: - MiMo-V2.5-Pro — 每次請求 790 個輸入 token、86,000 個快取 token、305 個輸出 token - Omen Alpha — 每次請求 300 個輸入 token、40,000 個快取 token、100 個輸出 token -這些預估值也基於以下每 1M tokens 的價格,以及每個模型所包含的每月使用量: - -| 模型 | 輸入 | 輸出 | 快取讀取 | 快取寫入 | 使用量 | -| --------------------------------------- | ------ | ------ | --------- | -------- | ------ | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | $15 | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | $15 | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | $15 | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | $15 | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | $60 | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | $15 | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | $60 | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | $60 | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | $15 | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | $60 | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | $60 | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | $60 | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | $60 | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | $15 | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | $60 | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | $60 | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | $60 | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | $15 | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | $30 | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | $30 | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | $60 | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | $60 | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | $60 | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | $60 | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | $15 | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | $15 | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $30 | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | $30 | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | $15 | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | $15 | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | $30 | -| Hy3 | $0.14 | $0.58 | $0.035 | - | $60 | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | $100 | - -**DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak 時段為週一至週五的 01:00-04:00 和 06:00-10:00 UTC;其他所有時段(包括週末)均為 Off-Peak。[了解更多](https://api-docs.deepseek.com/quick_start/pricing/)。 - -**DeepSeek V4 Flash Vision Exp:** 圖片會根據尺寸轉換為 token,並與文字 token 一起按輸入 token 計費。 [了解更多](https://api-docs.deepseek.com/quick_start/pricing/)。 - 您可以在 **console** 中追蹤您目前的使用量。 :::tip @@ -249,15 +251,15 @@ OpenCode Go 包含以下基準限制: --- -### 為什麼部分模型的使用量較低 +### 為什麼部分模型的使用額度較低 -使用 Go 時,您每月支付 $10;對大多數模型而言,我們的目標是提供相當於 6 倍費用的使用量。 +使用 Go 時,您每月支付 $10,包含的每月使用額度因模型而異。 -對大多數模型而言,我們透過大量採購折扣和預留 GPU 容量來達成此目標,再以 6 倍乘數將節省的成本回饋給您。 +對大多數模型而言,我們透過大量採購折扣和預留 GPU 容量來達成此目標,再透過提高每月使用額度,將節省的成本回饋給您。 對於部分模型,我們尚未有機會議定折扣或以較低成本託管,原因可能是模型剛推出,或其公開價格已經過折扣。 -對於這些模型,您獲得的使用量仍會比直接向模型供應商付費多一些;這就是上表中其使用量乘數較低的原因。 +對於這些模型,您獲得的使用額度仍會比直接向模型供應商付費多一些;這就是它們包含的每月使用額度較低的原因。 --- From b6914b39db86e196ebcc95e92a0188cdf58ef67a Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 9 Sep 2026 22:45:26 +0000 Subject: [PATCH 060/129] chore: generate --- packages/web/src/content/docs/ar/go.mdx | 72 ++++++------ packages/web/src/content/docs/bs/go.mdx | 72 ++++++------ packages/web/src/content/docs/da/go.mdx | 128 ++++++++++----------- packages/web/src/content/docs/de/go.mdx | 128 ++++++++++----------- packages/web/src/content/docs/es/go.mdx | 72 ++++++------ packages/web/src/content/docs/fr/go.mdx | 128 ++++++++++----------- packages/web/src/content/docs/go.mdx | 70 +++++------ packages/web/src/content/docs/it/go.mdx | 128 ++++++++++----------- packages/web/src/content/docs/ja/go.mdx | 58 +++++----- packages/web/src/content/docs/ko/go.mdx | 128 ++++++++++----------- packages/web/src/content/docs/nb/go.mdx | 128 ++++++++++----------- packages/web/src/content/docs/pl/go.mdx | 128 ++++++++++----------- packages/web/src/content/docs/pt-br/go.mdx | 72 ++++++------ packages/web/src/content/docs/ru/go.mdx | 128 ++++++++++----------- packages/web/src/content/docs/th/go.mdx | 128 ++++++++++----------- packages/web/src/content/docs/tr/go.mdx | 128 ++++++++++----------- packages/web/src/content/docs/zh-cn/go.mdx | 68 +++++------ packages/web/src/content/docs/zh-tw/go.mdx | 70 +++++------ 18 files changed, 917 insertions(+), 917 deletions(-) diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 1c8354ab3400..6fb053c3701b 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -137,42 +137,42 @@ OpenCode Go هو اشتراك منخفض التكلفة بقيمة **$10/شهر أسعار tokens مُدرجة لكل 1M tokens. | النموذج | الإدخال | الإخراج | القراءة المخزنة | الكتابة المخزنة | الحد الشهري | -| --------------------------------------- | ------- | ------- | --------------- | --------------- | --------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------- | ------- | --------------- | --------------- | ----------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** ساعات Peak هي 01:00-04:00 و06:00-10:00 UTC من الاثنين إلى الجمعة؛ وجميع الساعات الأخرى، بما في ذلك عطلات نهاية الأسبوع، Off-Peak. [اعرف المزيد](https://api-docs.deepseek.com/quick_start/pricing/). diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index 6ef94f809717..93c8cac498fc 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -147,42 +147,42 @@ Na primjer, ako model ima mjesečno ograničenje od $60, možete potrošiti do: Cijene tokena navedene su za 1M tokena. | Model | Input | Output | Cached Read | Cached Write | Mjesečno ograničenje | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | --------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | -------------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak sati su 01:00-04:00 i 06:00-10:00 UTC od ponedjeljka do petka; svi ostali sati, uključujući vikende, su Off-Peak. [Saznajte više](https://api-docs.deepseek.com/quick_start/pricing/). diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 6266d456653b..058f2f323f9d 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -147,42 +147,42 @@ Hvis en model for eksempel har en månedlig grænse på $60, kan du bruge op til Tokenpriser er angivet pr. 1M tokens. | Model | Input | Output | Cached Read | Cached Write | Månedlig grænse | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | --------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak-tiderne er 01:00-04:00 og 06:00-10:00 UTC fra mandag til fredag; alle andre tider, herunder weekender, er Off-Peak. [Læs mere](https://api-docs.deepseek.com/quick_start/pricing/). @@ -193,34 +193,34 @@ Tokenpriser er angivet pr. 1M tokens. Tabellen nedenfor viser et estimeret antal anmodninger baseret på typiske Go-forbrugsmønstre: | Model | anmodninger pr. 5 timer | anmodninger pr. uge | anmodninger pr. måned | -| ---------------------------- | ------------------- | ----------------- | ------------------ | -| Omen Alpha | 11,600 | 29,000 | 57,900 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| ---------------------------- | ----------------------- | ------------------- | --------------------- | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Estimaterne bruger følgende antal tokens pr. anmodning; det faktiske forbrug varierer. diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index 2921f3716db5..8c5080b042f6 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -139,42 +139,42 @@ Wenn ein Modell beispielsweise ein monatliches Limit von $60 hat, kannst du bis Tokenpreise gelten pro 1M Tokens. | Model | Input | Output | Cached Read | Cached Write | Monatliches Limit | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ----------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Die Peak-Zeiten sind montags bis freitags von 01:00-04:00 und 06:00-10:00 UTC; alle anderen Zeiten, einschließlich der Wochenenden, sind Off-Peak. [Mehr erfahren](https://api-docs.deepseek.com/quick_start/pricing/). @@ -185,34 +185,34 @@ Tokenpreise gelten pro 1M Tokens. Die folgende Tabelle enthält eine geschätzte Anzahl von Anfragen basierend auf typischen Go-Nutzungsmustern: | Model | Anfragen pro 5 Stunden | Anfragen pro Woche | Anfragen pro Monat | -| ---------------------------- | ------------------- | ----------------- | ------------------ | -| Omen Alpha | 11,600 | 29,000 | 57,900 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| ---------------------------- | ---------------------- | ------------------ | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Die Schätzungen verwenden die folgenden Token-Anzahlen pro Anfrage; die tatsächliche Nutzung variiert. diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index 304cbec24086..bddec12d6394 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -147,42 +147,42 @@ Por ejemplo, si un modelo tiene un límite mensual de $60, puedes gastar hasta: Los precios de tokens se indican por 1M tokens. | Modelo | Entrada | Salida | Lectura en caché | Escritura en caché | Límite mensual | -| --------------------------------------- | ------- | ------ | ---------------- | ------------------ | ---- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------- | ------ | ---------------- | ------------------ | -------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Las horas Peak son 01:00-04:00 y 06:00-10:00 UTC, de lunes a viernes; todas las demás horas, incluidos los fines de semana, son Off-Peak. [Más información](https://api-docs.deepseek.com/quick_start/pricing/). diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 05f472ede29e..4dcdbd0d6e67 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -137,42 +137,42 @@ Par exemple, si un modèle a une limite mensuelle de $60, vous pouvez utiliser j Les prix des tokens sont indiqués par million de tokens. | Modèle | Input | Output | Cached Read | Cached Write | Limite mensuelle | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ---------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Les heures Peak sont 01:00-04:00 et 06:00-10:00 UTC, du lundi au vendredi ; toutes les autres heures, y compris le week-end, sont Off-Peak. [En savoir plus](https://api-docs.deepseek.com/quick_start/pricing/). @@ -183,34 +183,34 @@ Les prix des tokens sont indiqués par million de tokens. Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur les habitudes d’utilisation typiques de Go : | Model | requêtes par 5 heures | requêtes par semaine | requêtes par mois | -| ---------------------------- | ------------------- | ----------------- | ------------------ | -| Omen Alpha | 11,600 | 29,000 | 57,900 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| ---------------------------- | --------------------- | -------------------- | ----------------- | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Les estimations utilisent les nombres de tokens suivants par requête ; l'utilisation réelle varie. diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index afa597baae52..afc7da0c9cce 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -150,41 +150,41 @@ Token prices are per 1M tokens. | Model | Input | Output | Cached Read | Cached Write | Monthly limit | | --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak hours are 01:00-04:00 and 06:00-10:00 UTC, Monday through Friday; all other hours, including weekends, are Off-Peak. [Learn more](https://api-docs.deepseek.com/quick_start/pricing/). diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index 154031e0e1bf..1a9007d73774 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -145,42 +145,42 @@ Ad esempio, se un modello ha un limite mensile di $60, puoi utilizzare fino a: I prezzi dei token sono indicati per 1M token. | Modello | Input | Output | Cached Read | Cached Write | Limite mensile | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | -------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Gli orari Peak sono 01:00-04:00 e 06:00-10:00 UTC, dal lunedì al venerdì; tutti gli altri orari, inclusi i fine settimana, sono Off-Peak. [Scopri di più](https://api-docs.deepseek.com/quick_start/pricing/). @@ -191,34 +191,34 @@ I prezzi dei token sono indicati per 1M token. La tabella seguente fornisce una stima del numero di richieste basata su pattern di utilizzo tipici di Go: | Model | richieste ogni 5 ore | richieste a settimana | richieste al mese | -| ---------------------------- | ------------------- | ----------------- | ------------------ | -| Omen Alpha | 11,600 | 29,000 | 57,900 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| ---------------------------- | -------------------- | --------------------- | ----------------- | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Le stime utilizzano i seguenti conteggi di token per richiesta; l'utilizzo effettivo varia. diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index 3dd3c966582a..e5334ab202b9 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -134,7 +134,7 @@ OpenCodeに加えて、以下のクライアントがOpenCode Goで正常に動 トークン単価は100万トークンあたりです。 | Model | Input | Output | Cached Read | Cached Write | 月間上限 | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | -------- | | Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | | GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | | GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | @@ -180,34 +180,34 @@ OpenCodeに加えて、以下のクライアントがOpenCode Goで正常に動 以下の表は、一般的なGoの利用パターンに基づく推定リクエスト数を示しています。 | Model | 5時間あたりのリクエスト数 | 週間リクエスト数 | 月間リクエスト数 | -| ---------------------------- | ------------------- | ----------------- | ------------------ | -| Omen Alpha | 11,600 | 29,000 | 57,900 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| ---------------------------- | ------------------------- | ---------------- | ---------------- | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | 推定値には、リクエストあたり以下のトークン数を使用しています。実際の使用量は異なります。 diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index 8a18c6665d94..64476530b9a9 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -134,42 +134,42 @@ OpenCode 외에도 다음 클라이언트가 OpenCode Go에서 올바르게 작 토큰 가격은 1M tokens 기준입니다. | Model | Input | Output | Cached Read | Cached Write | 월간 한도 | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | --------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak 시간은 월요일부터 금요일까지 01:00-04:00 및 06:00-10:00 UTC이며, 주말을 포함한 그 외 모든 시간은 Off-Peak입니다. [자세히 알아보기](https://api-docs.deepseek.com/quick_start/pricing/). @@ -180,34 +180,34 @@ OpenCode 외에도 다음 클라이언트가 OpenCode Go에서 올바르게 작 아래 표는 일반적인 Go 사용 패턴을 기준으로 한 예상 요청 횟수를 보여줍니다. | Model | 5시간당 요청 횟수 | 주간 요청 횟수 | 월간 요청 횟수 | -| ---------------------------- | ------------------- | ----------------- | ------------------ | -| Omen Alpha | 11,600 | 29,000 | 57,900 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| ---------------------------- | ----------------- | -------------- | -------------- | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | 예상치에는 요청당 다음 토큰 수를 사용하며, 실제 사용량은 달라질 수 있습니다. diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index ed4f636257c9..1a77ff0ed78d 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -147,42 +147,42 @@ Hvis en modell for eksempel har en månedlig grense på $60, kan du bruke opptil Tokenpriser er oppgitt per 1M tokens. | Model | Input | Output | Cached Read | Cached Write | Månedlig grense | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | --------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak-tidene er 01:00-04:00 og 06:00-10:00 UTC fra mandag til fredag; alle andre tider, inkludert helger, er Off-Peak. [Les mer](https://api-docs.deepseek.com/quick_start/pricing/). @@ -193,34 +193,34 @@ Tokenpriser er oppgitt per 1M tokens. Tabellen nedenfor viser 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 | -| ---------------------------- | ------------------- | ----------------- | ------------------ | -| Omen Alpha | 11,600 | 29,000 | 57,900 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| ---------------------------- | ------------------------ | -------------------- | ---------------------- | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Estimatene bruker følgende antall tokens per forespørsel; faktisk bruk varierer. diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index 5c60f7772368..7cb7b539b69d 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -141,42 +141,42 @@ Na przykład, jeśli model ma miesięczny limit $60, możesz wykorzystać do: Ceny tokenów podano za 1M tokenów. | Model | Wejście | Wyjście | Odczyt z cache | Zapis do cache | Limit miesięczny | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------- | ------- | -------------- | -------------- | ---------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Godziny Peak to 01:00-04:00 i 06:00-10:00 UTC od poniedziałku do piątku; wszystkie pozostałe godziny, w tym weekendy, to Off-Peak. [Dowiedz się więcej](https://api-docs.deepseek.com/quick_start/pricing/). @@ -187,34 +187,34 @@ Ceny tokenów podano za 1M tokenów. 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 | -| ---------------------------- | ------------------- | ----------------- | ------------------ | -| Omen Alpha | 11,600 | 29,000 | 57,900 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| ---------------------------- | ------------------- | ------------------ | ------------------ | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Szacunki wykorzystują następującą liczbę tokenów na żądanie; rzeczywiste użycie jest zmienne. diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index ab40591b53e6..8dd339cc56da 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -147,42 +147,42 @@ Por exemplo, se um modelo tiver um limite mensal de $60, você poderá gastar at Os preços de tokens são indicados por 1M tokens. | Modelo | Entrada | Saída | Leitura em cache | Escrita em cache | Limite mensal | -| --------------------------------------- | ------- | ------ | ---------------- | ---------------- | ---- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------- | ------ | ---------------- | ---------------- | ------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Os horários Peak são 01:00-04:00 e 06:00-10:00 UTC, de segunda a sexta-feira; todos os demais horários, incluindo os fins de semana, são Off-Peak. [Saiba mais](https://api-docs.deepseek.com/quick_start/pricing/). diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index f9a4996fc916..7dddac4b949a 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -147,42 +147,42 @@ OpenCode Go предназначен для [OpenCode](https://opencode.ai) и Цены на токены указаны за 1M токенов. | Model | Input | Output | Cached Read | Cached Write | Месячный лимит | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | -------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Часы Peak с понедельника по пятницу: 01:00-04:00 и 06:00-10:00 UTC; все остальные часы, включая выходные, относятся к Off-Peak. [Подробнее](https://api-docs.deepseek.com/quick_start/pricing/). @@ -193,34 +193,34 @@ OpenCode Go предназначен для [OpenCode](https://opencode.ai) и В таблице ниже приведено примерное число запросов на основе типичных сценариев использования Go: | Model | запросов за 5 часов | запросов в неделю | запросов в месяц | -| ---------------------------- | ------------------- | ----------------- | ------------------ | -| Omen Alpha | 11,600 | 29,000 | 57,900 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| ---------------------------- | ------------------- | ----------------- | ---------------- | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | В оценках используются следующие количества токенов на запрос; фактическое использование может отличаться. diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index 7246269cbc5a..d56836968eef 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -134,42 +134,42 @@ OpenCode Go ออกแบบมาสำหรับ [OpenCode](https://openco ราคา token แสดงต่อ 1M tokens | Model | Input | Output | Cached Read | Cached Write | ขีดจำกัดรายเดือน | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ---------------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** ช่วงเวลา Peak คือ 01:00-04:00 และ 06:00-10:00 UTC ตั้งแต่วันจันทร์ถึงวันศุกร์ ส่วนเวลาอื่นทั้งหมด รวมถึงวันหยุดสุดสัปดาห์ เป็น Off-Peak [ดูข้อมูลเพิ่มเติม](https://api-docs.deepseek.com/quick_start/pricing/) @@ -180,34 +180,34 @@ OpenCode Go ออกแบบมาสำหรับ [OpenCode](https://openco ตารางด้านล่างแสดงจำนวน request โดยประมาณตามรูปแบบการใช้งานปกติของ Go: | Model | requests ต่อ 5 ชั่วโมง | requests ต่อสัปดาห์ | requests ต่อเดือน | -| ---------------------------- | ------------------- | ----------------- | ------------------ | -| Omen Alpha | 11,600 | 29,000 | 57,900 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| ---------------------------- | ---------------------- | ------------------- | ----------------- | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | การประมาณการใช้จำนวน token ต่อ request ดังต่อไปนี้ การใช้งานจริงอาจแตกต่างกัน diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index fb21b09fc002..998eaa96b1c8 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -134,42 +134,42 @@ Her model için şu kullanım limitleri geçerlidir: 5 saatlik — aylık limiti Token fiyatları 1M token başına verilmiştir. | Model | Input | Output | Cached Read | Cached Write | Aylık limit | -| --------------------------------------- | ------ | ------ | ----------- | ------------ | ------------- | -| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| --------------------------------------- | ------ | ------ | ----------- | ------------ | ----------- | +| Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak saatleri pazartesiden cumaya 01:00-04:00 ve 06:00-10:00 UTC'dir; hafta sonları dahil diğer tüm saatler Off-Peak'tir. [Daha fazla bilgi](https://api-docs.deepseek.com/quick_start/pricing/). @@ -180,34 +180,34 @@ Token fiyatları 1M token başına verilmiştir. Aşağıdaki tabloda tipik Go kullanım modellerine dayalı tahmini istek sayısı verilmiştir: | Model | 5 saatte bir istek | haftalık istek | aylık istek | -| ---------------------------- | ------------------- | ----------------- | ------------------ | -| Omen Alpha | 11,600 | 29,000 | 57,900 | -| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | -| GLM-5.3 | 220 | 540 | 1,080 | -| GLM-5.2 | 880 | 2,150 | 4,300 | -| GLM-5.1 | 880 | 2,150 | 4,300 | -| Kimi K3 | 110 | 250 | 490 | -| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | -| Kimi K2.6 | 1,150 | 2,880 | 5,750 | -| LongCat-2.0 | 11,400 | 28,600 | 57,200 | -| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | -| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | -| MiniMax M3 | 3,200 | 8,000 | 16,000 | -| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | -| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | -| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | -| Qwen3.8 Max | 160 | 400 | 810 | -| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | -| Qwen3.7 Max | 170 | 420 | 840 | -| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | -| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | -| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | -| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | -| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | -| Hy4 preview | 1,350 | 3,380 | 6,770 | -| Hy3 | 4,300 | 10,750 | 21,500 | -| Grok 4.6 | 169 | 423 | 845 | -| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | +| ---------------------------- | ------------------ | -------------- | ----------- | +| Omen Alpha | 11,600 | 29,000 | 57,900 | +| GLM-5.3-Flash | 6,320 | 15,790 | 31,580 | +| GLM-5.3 | 220 | 540 | 1,080 | +| GLM-5.2 | 880 | 2,150 | 4,300 | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| Kimi K3 | 110 | 250 | 490 | +| Kimi K2.7 Code | 1,350 | 3,380 | 6,750 | +| Kimi K2.6 | 1,150 | 2,880 | 5,750 | +| LongCat-2.0 | 11,400 | 28,600 | 57,200 | +| MiMo-V2.5 | 30,100 | 75,200 | 150,400 | +| MiMo-V2.5-Pro | 3,250 | 8,150 | 16,300 | +| MiniMax M3 | 3,200 | 8,000 | 16,000 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| Muse Spark 1.3 Contributor | 45,300 | 113,300 | 226,600 | +| Muse Spark 1.2 Contributor | 45,300 | 113,300 | 226,600 | +| Qwen3.8 Max | 160 | 400 | 810 | +| Qwen3.8 Flash | 5,400 | 13,500 | 27,000 | +| Qwen3.7 Max | 170 | 420 | 840 | +| Qwen3.7 Plus | 4,300 | 10,800 | 21,600 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| DeepSeek V4 Pro | 1,050 | 2,600 | 5,200 | +| DeepSeek V4 Flash | 7,600 | 18,900 | 37,800 | +| DeepSeek V4 Flash Vision Exp | 3,800 | 9,450 | 18,900 | +| Hy4 preview | 1,350 | 3,380 | 6,770 | +| Hy3 | 4,300 | 10,750 | 21,500 | +| Grok 4.6 | 169 | 423 | 845 | +| GPT 5.6 Luna | 2,050 | 5,100 | 10,250 | Tahminler istek başına aşağıdaki token sayılarını kullanır; gerçek kullanım değişir. diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index ad4ffe237d5c..d308f193d6fa 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -135,40 +135,40 @@ Token 价格按每 1M tokens 列示。 | 模型 | 输入 | 输出 | 缓存读取 | 缓存写入 | 每月限制 | | --------------------------------------- | ------ | ------ | --------- | -------- | -------- | | Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak 时段为周一至周五的 01:00-04:00 和 06:00-10:00 UTC;其他所有时段(包括周末)均为 Off-Peak。[了解更多](https://api-docs.deepseek.com/quick_start/pricing/)。 diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index 349336f8ff3a..255650be197b 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -133,42 +133,42 @@ OpenCode Go 適用於 [OpenCode](https://opencode.ai) 以及其他會產生類 Token 價格按每 1M tokens 列示。 | 模型 | 輸入 | 輸出 | 快取讀取 | 快取寫入 | 每月限制 | -| --------------------------------------- | ------ | ------ | --------- | -------- | ------ | +| --------------------------------------- | ------ | ------ | --------- | -------- | -------- | | Omen Alpha | $0.20 | $0.66 | $0.04 | - | **$100** | -| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | -| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | -| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | -| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | -| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | -| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | -| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | -| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | -| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | -| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | -| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | -| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | -| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | -| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | -| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | -| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | -| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | -| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | -| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | -| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | -| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | -| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | -| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | -| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | -| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | -| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | -| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | -| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | -| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | -| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | -| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | -| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | +| GLM-5.3-Flash | $0.15 | $0.50 | $0.03 | - | **$60** | +| GLM-5.3 | $1.40 | $4.40 | $0.26 | - | **$15** | +| GLM-5.2 | $1.40 | $4.40 | $0.26 | - | **$60** | +| GLM-5.1 | $1.40 | $4.40 | $0.26 | - | **$60** | +| Kimi K3 | $3.00 | $15.00 | $0.30 | - | **$15** | +| Kimi K2.7 Code | $0.95 | $4.00 | $0.19 | - | **$60** | +| Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | **$60** | +| LongCat-2.0 | $0.30 | $1.20 | $0.006 | - | **$60** | +| MiMo V2.5 | $0.14 | $0.28 | $0.0028 | - | **$60** | +| MiMo V2.5 Pro | $0.435 | $0.87 | $0.003625 | - | **$15** | +| MiniMax M3 | $0.30 | $1.20 | $0.06 | - | **$60** | +| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | **$60** | +| Muse Spark 1.3 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Muse Spark 1.2 Contributor | $0.10 | $0.20 | $0.002 | - | **$60** | +| Qwen3.8 Max | $2.00 | $6.00 | $0.25 | $2.50 | **$15** | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | **$30** | +| Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | **$30** | +| Qwen3.7 Plus (≤ 256K tokens) | $0.40 | $1.60 | $0.04 | $0.50 | **$60** | +| Qwen3.7 Plus (> 256K tokens) | $1.20 | $4.80 | $0.12 | $1.50 | **$60** | +| Qwen3.6 Plus (≤ 256K tokens) | $0.50 | $3.00 | $0.05 | $0.625 | **$60** | +| Qwen3.6 Plus (> 256K tokens) | $2.00 | $6.00 | $0.20 | $2.50 | **$60** | +| DeepSeek V4 Pro (Off-Peak) | $0.66 | $1.98 | $0.022 | - | **$15** | +| DeepSeek V4 Pro (Peak) | $1.32 | $3.96 | $0.044 | - | **$15** | +| DeepSeek V4 Flash (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$30** | +| DeepSeek V4 Flash (Peak) | $0.44 | $1.32 | $0.014 | - | **$30** | +| DeepSeek V4 Flash Vision Exp (Off-Peak) | $0.22 | $0.66 | $0.007 | - | **$15** | +| DeepSeek V4 Flash Vision Exp (Peak) | $0.44 | $1.32 | $0.014 | - | **$15** | +| Hy4 preview | $0.834 | $2.501 | $0.042 | - | **$30** | +| Hy3 | $0.14 | $0.58 | $0.035 | - | **$60** | +| Grok 4.6 (≤ 200K tokens) | $2.00 | $6.00 | $0.50 | - | **$15** | +| Grok 4.6 (> 200K tokens) | $4.00 | $12.00 | $1.00 | - | **$15** | +| GPT 5.6 Luna (≤ 272K tokens) | $0.20 | $1.20 | $0.02 | $0.25 | **$15** | +| GPT 5.6 Luna (> 272K tokens) | $0.40 | $1.80 | $0.04 | $0.50 | **$15** | **DeepSeek V4 Flash / V4 Flash Vision Exp / V4 Pro:** Peak 時段為週一至週五的 01:00-04:00 和 06:00-10:00 UTC;其他所有時段(包括週末)均為 Off-Peak。[了解更多](https://api-docs.deepseek.com/quick_start/pricing/)。 From a9a6fad0fae42af99b9f4b1d4ff519a979f16c90 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 9 Sep 2026 21:51:42 -0500 Subject: [PATCH 061/129] fix(opencode): request summarized adaptive thinking (#48269) Co-authored-by: Aljosha Friemann <1730315+afriemann@users.noreply.github.com> --- packages/opencode/src/plugin/github-copilot/models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/plugin/github-copilot/models.ts b/packages/opencode/src/plugin/github-copilot/models.ts index 870e3f87a0fd..8cafa567150b 100644 --- a/packages/opencode/src/plugin/github-copilot/models.ts +++ b/packages/opencode/src/plugin/github-copilot/models.ts @@ -176,7 +176,7 @@ function build(key: string, remote: SelectableItem, url: string, prev?: Model): variants[effort] = { thinking: { type: "adaptive", - ...(model.api.id.includes("opus-4.7") ? { display: "summarized" } : {}), + display: "summarized", }, effort, } From 28a62b71489dd952e01369acdd71e9d686440b28 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 10 Sep 2026 11:05:41 +0800 Subject: [PATCH 062/129] feat(go): add DeepSeek V4.1 Flash (#48270) --- .../console/app/src/component/go-models.ts | 15 ++++++++++-- .../app/src/component/limits-graph.css | 7 ++++++ .../app/src/component/limits-graph.tsx | 9 ++++++++ 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/uk.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 | 4 ---- packages/console/app/src/routes/go/index.tsx | 8 +++++++ .../routes/workspace/[id]/go/lite-section.tsx | 2 ++ packages/web/src/content/docs/ar/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/bs/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/da/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/de/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/es/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/fr/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/go.mdx | 21 +++++++++++------ packages/web/src/content/docs/it/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/ja/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/ko/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/nb/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/pl/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/pt-br/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/ru/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/th/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/tr/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/zh-cn/go.mdx | 23 ++++++++++++------- packages/web/src/content/docs/zh-tw/go.mdx | 23 ++++++++++++------- 42 files changed, 344 insertions(+), 149 deletions(-) diff --git a/packages/console/app/src/component/go-models.ts b/packages/console/app/src/component/go-models.ts index 0aa2f1704b99..db081edb678b 100644 --- a/packages/console/app/src/component/go-models.ts +++ b/packages/console/app/src/component/go-models.ts @@ -16,12 +16,23 @@ export const goModels = [ { id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", requests: 3250, allowance: 15 }, { id: "qwen3.6-plus", name: "Qwen3.6 Plus", requests: 3300, allowance: 60 }, { id: "minimax-m2.7", name: "MiniMax M2.7", requests: 3400, allowance: 60 }, - { id: "deepseek-v4-flash-vision-exp", name: "DeepSeek V4 Flash Vision Exp", requests: 3800, allowance: 15 }, + { id: "deepseek-v4-flash-vision-exp", name: "DeepSeek V4 Flash Vision Exp", requests: 6500, allowance: 15 }, { id: "qwen3.7-plus", name: "Qwen3.7 Plus", requests: 4300, allowance: 60, featured: true }, { id: "hy3", name: "Hy3", requests: 4300, allowance: 60 }, { id: "qwen3.8-flash", name: "Qwen3.8 Flash", requests: 5400, allowance: 30 }, { id: "glm-5.3-flash", name: "GLM-5.3-Flash", requests: 6320, allowance: 60, featured: true }, - { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", requests: 7600, allowance: 30, featured: true }, + { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", requests: 13000, allowance: 30, featured: true }, + { + id: "deepseek-flash", + name: "DeepSeek V4.1 Flash", + requests: 26000, + baseRequests: 6500, + allowance: 60, + baseAllowance: 15, + bonus: 4, + featured: true, + fresh: true, + }, { id: "longcat-2.0", name: "LongCat-2.0", requests: 11400, allowance: 60 }, { id: "omen-alpha", name: "Omen Alpha", requests: 11600, allowance: 100, featured: true, fresh: true }, { id: "mimo-v2.5", name: "MiMo-V2.5", requests: 30100, allowance: 60, featured: true }, diff --git a/packages/console/app/src/component/limits-graph.css b/packages/console/app/src/component/limits-graph.css index c03996c96e6c..ddcf2dfece52 100644 --- a/packages/console/app/src/component/limits-graph.css +++ b/packages/console/app/src/component/limits-graph.css @@ -25,6 +25,13 @@ } } + s { + display: block; + color: var(--color-text-weak); + font-size: 0.6875rem; + font-weight: 400; + } + [data-slot="columns"], [data-slot="model-row"] { display: grid; diff --git a/packages/console/app/src/component/limits-graph.tsx b/packages/console/app/src/component/limits-graph.tsx index b10de2969685..410fd48e4392 100644 --- a/packages/console/app/src/component/limits-graph.tsx +++ b/packages/console/app/src/component/limits-graph.tsx @@ -80,6 +80,9 @@ export function LimitsGraph(props: { href: string }) { {i18n.t("go.graph.new")} + + {i18n.t("go.graph.bonus", { count: model.bonus! })} +
    + + {format().format(model.baseRequests!)}{" "} + {format().format(model.requests)}
    = 60 ? "" : undefined}> + + {currency().format(model.baseAllowance!)}{" "} + {(part) => ( diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index 035794a23bb1..fac1d6475833 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "يحصل DeepSeek V4.1 Flash على حدود استخدام مضاعفة 4 مرات لفترة محدودة", + "go.graph.bonus": "استخدام مضاعف {{count}} مرات", "nav.github": "GitHub", "nav.docs": "الوثائق", "nav.changelog": "سجل التغييرات", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index 9afa3e27d8af..a2e5867a63dc 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash tem limites de uso 4x maiores por tempo limitado", + "go.graph.bonus": "{{count}}× mais uso", "nav.github": "GitHub", "nav.docs": "Documentação", "nav.changelog": "Changelog", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index b7b39f7b0e8d..d2787bb611eb 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash får firedoblet brugsgrænse i en begrænset periode", + "go.graph.bonus": "{{count}}× forbrug", "nav.github": "GitHub", "nav.docs": "Dokumentation", "nav.changelog": "Changelog", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index 9f31cd89ec34..8fb78e76e611 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash erhält für begrenzte Zeit 4x Nutzungslimits", + "go.graph.bonus": "{{count}}× Nutzung", "nav.github": "GitHub", "nav.docs": "Dokumentation", "nav.changelog": "Changelog", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index 44cd2d74eb56..4469c3353c51 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -1,4 +1,6 @@ export const dict = { + "go.promo.deepseek": "DeepSeek V4.1 Flash gets 4× usage limits for a limited time", + "go.graph.bonus": "{{count}}× usage", "nav.github": "GitHub", "nav.docs": "Docs", "nav.data": "Data", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 89634e4f9c84..9750bc584e27 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash tiene límites de uso 4x mayores por tiempo limitado", + "go.graph.bonus": "{{count}}× de uso", "nav.github": "GitHub", "nav.docs": "Documentación", "nav.changelog": "Registro de cambios", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index 5c0c3e95951a..5281b6b3e54f 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash bénéficie de limites d’utilisation 4x supérieures pour une durée limitée", + "go.graph.bonus": "{{count}}× d’utilisation", "app.meta.description": "OpenCode - L'agent de code open source.", "nav.github": "GitHub", "nav.docs": "Documentation", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 82d32ab25e4f..cb2f3d7d7a83 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash offre limiti di utilizzo 4x superiori per un periodo limitato", + "go.graph.bonus": "Utilizzo {{count}}×", "nav.github": "GitHub", "nav.docs": "Documentazione", "nav.changelog": "Changelog", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index ba0491b97426..2ae754c47d90 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flashの利用上限が期間限定で4倍に", + "go.graph.bonus": "利用枠{{count}}倍", "nav.github": "GitHub", "nav.docs": "ドキュメント", "nav.changelog": "変更履歴", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index c0fe7f887379..e9b5c92071c4 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash 사용 한도가 한시적으로 4배 확대됩니다", + "go.graph.bonus": "사용량 {{count}}배", "nav.github": "GitHub", "nav.docs": "문서", "nav.changelog": "변경 내역", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index 801eed55740e..fc2636a30057 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash får 4x bruksgrense i en begrenset periode", + "go.graph.bonus": "{{count}}× bruk", "nav.github": "GitHub", "nav.docs": "Dokumentasjon", "nav.changelog": "Endringslogg", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index 8716919d5c92..4a4e1ba724a6 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -2,6 +2,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash oferuje 4x wyższe limity użycia przez ograniczony czas", + "go.graph.bonus": "{{count}}× większy limit", "nav.github": "GitHub", "nav.docs": "Dokumentacja", "nav.changelog": "Dziennik zmian", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index df3f4fbc99c1..37f6e43b06d6 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash получает 4x лимиты использования на ограниченное время", + "go.graph.bonus": "Лимит ×{{count}}", "nav.github": "GitHub", "nav.docs": "Документация", "nav.changelog": "Список изменений", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index 4c1758d269fb..97a80f039418 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash เพิ่มโควตาการใช้งานเป็น 4 เท่าในช่วงเวลาจำกัด", + "go.graph.bonus": "ใช้งาน {{count}} เท่า", "nav.github": "GitHub", "nav.docs": "เอกสาร", "nav.changelog": "บันทึกการเปลี่ยนแปลง", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index 3748ba8d8ade..4289706cc66e 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash sınırlı bir süre için 4x kullanım limiti sunuyor", + "go.graph.bonus": "{{count}}× kullanım", "nav.github": "GitHub", "nav.docs": "Dokümantasyon", "nav.changelog": "Değişiklik günlüğü", diff --git a/packages/console/app/src/i18n/uk.ts b/packages/console/app/src/i18n/uk.ts index 0c23398ce9f6..4efac1d76762 100644 --- a/packages/console/app/src/i18n/uk.ts +++ b/packages/console/app/src/i18n/uk.ts @@ -2,6 +2,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash отримує 4x ліміти використання протягом обмеженого часу", + "go.graph.bonus": "Ліміт ×{{count}}", "nav.github": "GitHub", "nav.docs": "Документація", "nav.changelog": "Журнал змін", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index 887911f04bd6..8c1aff6f6083 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash 限时享受 4 倍使用额度", + "go.graph.bonus": "{{count}} 倍用量", "nav.github": "GitHub", "nav.docs": "文档", "nav.changelog": "更新日志", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index 742cd3b1da87..a4a5f4bd4a73 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -3,6 +3,8 @@ import { dict as en } from "./en" export const dict = { ...en, + "go.promo.deepseek": "DeepSeek V4.1 Flash 限時享有 4 倍使用額度", + "go.graph.bonus": "{{count}} 倍用量", "nav.github": "GitHub", "nav.docs": "文件", "nav.changelog": "更新日誌", diff --git a/packages/console/app/src/routes/go/index.css b/packages/console/app/src/routes/go/index.css index 8193441c941c..6793b6c32bb1 100644 --- a/packages/console/app/src/routes/go/index.css +++ b/packages/console/app/src/routes/go/index.css @@ -363,10 +363,6 @@ body { [data-slot="text"] { color: var(--color-text-strong); line-height: 1.4; - - @media (max-width: 30.625rem) { - display: none; - } } } diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index f80bde082499..ba8f9302b46f 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -46,8 +46,10 @@ const models = [ { name: "MiniMax M2.7", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" }, { name: "Muse Spark 1.3 Contributor", training: "go.faq.a5.used", retention: "go.faq.a5.notZdr" }, { name: "Muse Spark 1.2 Contributor", training: "go.faq.a5.used", retention: "go.faq.a5.notZdr" }, + { name: "DeepSeek V4.1 Flash", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" }, { name: "DeepSeek V4 Pro", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" }, { name: "DeepSeek V4 Flash", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" }, + { name: "DeepSeek V4 Flash Vision Exp", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" }, { name: "Hy4 preview", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" }, { name: "Hy3", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" }, { name: "Omen Alpha", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" }, @@ -80,6 +82,12 @@ export default function Home() {
    +
    + {i18n.t("home.banner.badge")} +
    + {i18n.t("go.promo.deepseek")} +
    +
    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 868961777ce5..335a51fa632e 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 @@ -659,6 +659,7 @@ export function LiteSection(props: { lite: LiteSubscription | undefined }) {
  • Qwen3.7 Max
  • Qwen3.7 Plus
  • Qwen3.6 Plus
  • +
  • DeepSeek V4.1 Flash — {i18n.t("go.graph.bonus", { count: 4 })}
  • DeepSeek V4 Pro
  • DeepSeek V4 Flash
  • DeepSeek V4 Flash Vision Exp
  • @@ -668,6 +669,7 @@ export function LiteSection(props: { lite: LiteSubscription | undefined }) {
  • Hy3
  • Omen Alpha
  • +

    {i18n.t("go.promo.deepseek")}

    {i18n.t("workspace.lite.promo.footer")}

    + - - - - diff --git a/packages/console/app/src/routes/index.css b/packages/console/app/src/routes/index.css index d06e2659533b..9d063f8e4685 100644 --- a/packages/console/app/src/routes/index.css +++ b/packages/console/app/src/routes/index.css @@ -490,77 +490,6 @@ 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="platforms"] { - @media (max-width: 49.125rem) { - display: none; - } - } - - [data-slot="link"] { - color: var(--color-text-weak); - white-space: nowrap; - text-decoration: none; - - @media (max-width: 30.625rem) { - display: none; - } - } - - [data-slot="link"]:hover { - color: var(--color-text); - text-decoration: underline; - text-underline-offset: 2px; - text-decoration-thickness: 1px; - } - - [data-slot="link-mobile"] { - display: none; - color: var(--color-text-strong); - white-space: nowrap; - text-decoration: none; - - @media (max-width: 30.625rem) { - display: inline; - } - } - - [data-slot="link-mobile"]:hover { - text-decoration: underline; - text-underline-offset: 2px; - text-decoration-thickness: 1px; - } - } - [data-slot="hero-copy"] { [data-slot="releases"] { background: none; diff --git a/packages/console/app/src/routes/index.tsx b/packages/console/app/src/routes/index.tsx index c046a56a2917..99ec06895f98 100644 --- a/packages/console/app/src/routes/index.tsx +++ b/packages/console/app/src/routes/index.tsx @@ -54,22 +54,6 @@ export default function Home() {
    -
    - {i18n.t("home.banner.badge")} -
    - - {i18n.t("home.banner.text")} - {i18n.t("home.banner.platforms")}. - - - {i18n.t("home.banner.downloadNow")} - - - {i18n.t("home.banner.downloadBetaNow")} - -
    -
    -
    {/* paru + + yay +
    @@ -115,7 +102,7 @@ export default function Home() { curl -fsSL https:// - opencode.ai/install + opencode.ai/v2/install | bash @@ -124,8 +111,8 @@ export default function Home() { @@ -133,8 +120,8 @@ export default function Home() { @@ -143,7 +130,7 @@ export default function Home() { @@ -152,7 +139,16 @@ export default function Home() { + + + diff --git a/packages/web/config.mjs b/packages/web/config.mjs index 08ab45bda774..c75181d23f95 100644 --- a/packages/web/config.mjs +++ b/packages/web/config.mjs @@ -9,6 +9,6 @@ export default { discord: "https://opencode.ai/discord", headerLinks: [ { name: "app.header.home", url: "/" }, - { name: "app.header.docs", url: "/docs/" }, + { name: "app.header.docs", url: "/v2/docs" }, ], } From ae2da69004ca000897c59666c8b633b0b82d10c7 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 19 Sep 2026 11:30:51 +0800 Subject: [PATCH 099/129] docs: add Qwen3.8 Flash to Zen (#49888) --- 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, 36 insertions(+) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index adb3ed6c5fa0..f41cc35db7ad 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -99,6 +99,7 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -163,6 +164,7 @@ https://opencode.ai/zen/v1/models | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index fcdd6ef0643a..c066f72e6844 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -104,6 +104,7 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -170,6 +171,7 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 88708c5461ba..766648fc60cf 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -104,6 +104,7 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -170,6 +171,7 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index 9cb06fa2ee69..f5f73b89a928 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -95,6 +95,7 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -159,6 +160,7 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index c7e25c620d3d..62e9c4319f17 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -104,6 +104,7 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -170,6 +171,7 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index e866a2896ca7..382feb55859f 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -95,6 +95,7 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -159,6 +160,7 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index d5f5a4f6da0d..32bdf7db86d6 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -104,6 +104,7 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -170,6 +171,7 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index 7074c9d39ca3..d97a80bfc74e 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -95,6 +95,7 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -159,6 +160,7 @@ https://opencode.ai/zen/v1/models | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 4667f70a39e0..f5975863feaa 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -95,6 +95,7 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -159,6 +160,7 @@ https://opencode.ai/zen/v1/models | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index 21bea7b56f0b..e643a39d06f4 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -104,6 +104,7 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -170,6 +171,7 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 330ebcc4c1e1..37f5979741ce 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -104,6 +104,7 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -170,6 +171,7 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index 336539d3c82b..74ae94ea7f97 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -95,6 +95,7 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -159,6 +160,7 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 5497bb27d8a3..44c6acca8805 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -104,6 +104,7 @@ OpenCode Zen работает как любой другой провайдер | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -170,6 +171,7 @@ https://opencode.ai/zen/v1/models | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index c5d7c45d651a..716600c86b31 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -97,6 +97,7 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -161,6 +162,7 @@ https://opencode.ai/zen/v1/models | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index fc51004c6858..487670ec0c8f 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -95,6 +95,7 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -159,6 +160,7 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 0f1f0cd9112a..0fc5110453fe 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -104,6 +104,7 @@ You can also access our models through the following API endpoints. | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -170,6 +171,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index 20633889fb9f..a9529ed09ffc 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -95,6 +95,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -159,6 +160,7 @@ https://opencode.ai/zen/v1/models | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 6077ae07d55c..ae2c802beabd 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -99,6 +99,7 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Grok Build 0.1 | grok-build-0.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.3 | muse-spark-1.3 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | | Muse Spark 1.2 | muse-spark-1.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` | +| Qwen3.8 Flash | qwen3.8-flash | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Max | qwen3.7-max | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | @@ -164,6 +165,7 @@ https://opencode.ai/zen/v1/models | Kimi K3 | $3.00 | $15.00 | $0.30 | - | | Kimi K2.6 | $0.95 | $4.00 | $0.16 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | +| Qwen3.8 Flash | $0.15 | $0.47 | $0.016 | $0.20 | | Qwen3.7 Max | $2.50 | $7.50 | $0.50 | $3.125 | | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | From a6cad7743f8eceee5e10828d78f6db79a5fed2d5 Mon Sep 17 00:00:00 2001 From: Daniel Chen Date: Fri, 18 Sep 2026 21:15:46 -0700 Subject: [PATCH 100/129] docs: add DeepSeek V4.1 Flash to Zen (#49897) --- 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, 36 insertions(+) diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index f41cc35db7ad..dd1f90ddf900 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -104,6 +104,7 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -169,6 +170,7 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index c066f72e6844..08b41f4fe9eb 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -109,6 +109,7 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -176,6 +177,7 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 766648fc60cf..05ea77160391 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -109,6 +109,7 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -176,6 +177,7 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index f5f73b89a928..a2f3ee4e46b2 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -100,6 +100,7 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -165,6 +166,7 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 62e9c4319f17..35d92911ce31 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -109,6 +109,7 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -176,6 +177,7 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index 382feb55859f..9d9d097768b7 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -100,6 +100,7 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -165,6 +166,7 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 32bdf7db86d6..9c915f9582ac 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -109,6 +109,7 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -176,6 +177,7 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index d97a80bfc74e..dec388a58889 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -100,6 +100,7 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -165,6 +166,7 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index f5975863feaa..363665cb2e8a 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -100,6 +100,7 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -165,6 +166,7 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index e643a39d06f4..1322f15b8004 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -109,6 +109,7 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -176,6 +177,7 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 37f5979741ce..cc8b57187eaa 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -109,6 +109,7 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -176,6 +177,7 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index 74ae94ea7f97..1fa23a5b2a10 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -100,6 +100,7 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -165,6 +166,7 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 44c6acca8805..2d7a02c74725 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -109,6 +109,7 @@ OpenCode Zen работает как любой другой провайдер | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -176,6 +177,7 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index 716600c86b31..e380d5e5ed81 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -102,6 +102,7 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -167,6 +168,7 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index 487670ec0c8f..d7ea730097dc 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -100,6 +100,7 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -165,6 +166,7 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 0fc5110453fe..4c0c5a9f1b30 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -109,6 +109,7 @@ You can also access our models through the following API endpoints. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -176,6 +177,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**. | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index a9529ed09ffc..6b3357387040 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -100,6 +100,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -165,6 +166,7 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index ae2c802beabd..f93a553df7a4 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -104,6 +104,7 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -170,6 +171,7 @@ https://opencode.ai/zen/v1/models | Qwen3.7 Plus | $0.40 | $1.60 | $0.04 | $0.50 | | Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | | Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | +| DeepSeek V4.1 Flash | $0.30 | $1.20 | $0.006 | - | | DeepSeek V4 Pro | $1.74 | $3.48 | $0.145 | - | | DeepSeek V4 Flash | $0.14 | $0.28 | $0.028 | - | | DeepSeek V4 Flash Vision Exp | $0.14 | $0.28 | $0.028 | - | From 4e1c49630a5604686273913cafadae081a43c054 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Sat, 19 Sep 2026 04:17:02 +0000 Subject: [PATCH 101/129] chore: generate --- 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 dd1f90ddf900..6c9e250f83d5 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -104,7 +104,7 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index 08b41f4fe9eb..b3f5293e75ca 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -109,7 +109,7 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 05ea77160391..41648561c607 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -109,7 +109,7 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index a2f3ee4e46b2..97e09eb5df9f 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -100,7 +100,7 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index 35d92911ce31..f2bc93b184ae 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -109,7 +109,7 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index 9d9d097768b7..f5df82a0db84 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -100,7 +100,7 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index 9c915f9582ac..fba40e550029 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -109,7 +109,7 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index dec388a58889..d36e3819920b 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -100,7 +100,7 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index 363665cb2e8a..fb74d67c7720 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -100,7 +100,7 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index 1322f15b8004..abee25ed3e61 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -109,7 +109,7 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index cc8b57187eaa..1115e8675951 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -109,7 +109,7 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index 1fa23a5b2a10..7e5bd69bc041 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -100,7 +100,7 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 2d7a02c74725..0c94972196d1 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -109,7 +109,7 @@ OpenCode Zen работает как любой другой провайдер | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index e380d5e5ed81..523bd50c62e7 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -102,7 +102,7 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index d7ea730097dc..3565259b713b 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -100,7 +100,7 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index 4c0c5a9f1b30..9aa2ac101991 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -109,7 +109,7 @@ You can also access our models through the following API endpoints. | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index 6b3357387040..8700749a9175 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -100,7 +100,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index f93a553df7a4..d849c5e7c8e9 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -104,7 +104,7 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Qwen3.7 Plus | qwen3.7-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | -| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | +| DeepSeek V4.1 Flash | deepseek-v4.1-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Pro | deepseek-v4-pro | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash | deepseek-v4-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | DeepSeek V4 Flash Vision Exp | deepseek-v4-flash-vision-exp | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | From f1aacaba22af56394f9bb8334e12e9653096236d Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 18 Sep 2026 21:35:13 -0400 Subject: [PATCH 102/129] feat(web): add v2 announcement banner to legacy docs --- packages/web/astro.config.mjs | 1 + packages/web/src/components/Banner.astro | 61 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 packages/web/src/components/Banner.astro diff --git a/packages/web/astro.config.mjs b/packages/web/astro.config.mjs index 2b39c60d6b8d..a6144fd17330 100644 --- a/packages/web/astro.config.mjs +++ b/packages/web/astro.config.mjs @@ -296,6 +296,7 @@ export default defineConfig({ ], components: { Hero: "./src/components/Hero.astro", + Banner: "./src/components/Banner.astro", Head: "./src/components/Head.astro", Header: "./src/components/Header.astro", Footer: "./src/components/Footer.astro", diff --git a/packages/web/src/components/Banner.astro b/packages/web/src/components/Banner.astro new file mode 100644 index 000000000000..05857bd850ef --- /dev/null +++ b/packages/web/src/components/Banner.astro @@ -0,0 +1,61 @@ + + New + OpenCode v2 is now available + + + + From 6da6d47aab3d1a92f3aaab439922f7cd725c25f0 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 19 Sep 2026 03:29:17 -0400 Subject: [PATCH 103/129] fix(web): render v2 banner full width below header --- packages/web/astro.config.mjs | 2 +- packages/web/src/components/Banner.astro | 61 ------------- packages/web/src/components/PageFrame.astro | 97 +++++++++++++++++++++ packages/web/src/components/V2Banner.astro | 85 ++++++++++++++++++ packages/web/src/styles/custom.css | 26 ++++++ 5 files changed, 209 insertions(+), 62 deletions(-) delete mode 100644 packages/web/src/components/Banner.astro create mode 100644 packages/web/src/components/PageFrame.astro create mode 100644 packages/web/src/components/V2Banner.astro diff --git a/packages/web/astro.config.mjs b/packages/web/astro.config.mjs index a6144fd17330..21eaa6e65116 100644 --- a/packages/web/astro.config.mjs +++ b/packages/web/astro.config.mjs @@ -296,7 +296,7 @@ export default defineConfig({ ], components: { Hero: "./src/components/Hero.astro", - Banner: "./src/components/Banner.astro", + PageFrame: "./src/components/PageFrame.astro", Head: "./src/components/Head.astro", Header: "./src/components/Header.astro", Footer: "./src/components/Footer.astro", diff --git a/packages/web/src/components/Banner.astro b/packages/web/src/components/Banner.astro deleted file mode 100644 index 05857bd850ef..000000000000 --- a/packages/web/src/components/Banner.astro +++ /dev/null @@ -1,61 +0,0 @@ - - New - OpenCode v2 is now available - - - - diff --git a/packages/web/src/components/PageFrame.astro b/packages/web/src/components/PageFrame.astro new file mode 100644 index 000000000000..49dc4195f028 --- /dev/null +++ b/packages/web/src/components/PageFrame.astro @@ -0,0 +1,97 @@ +--- +import MobileMenuToggle from "virtual:starlight/components/MobileMenuToggle" +import V2Banner from "./V2Banner.astro" + +const { hasSidebar } = Astro.locals.starlightRoute +--- + +
    +
    + + { + hasSidebar && ( + + ) + } +
    +
    + + diff --git a/packages/web/src/components/V2Banner.astro b/packages/web/src/components/V2Banner.astro new file mode 100644 index 000000000000..11ce915b6680 --- /dev/null +++ b/packages/web/src/components/V2Banner.astro @@ -0,0 +1,85 @@ + + New + OpenCode v2 is now available + + + + diff --git a/packages/web/src/styles/custom.css b/packages/web/src/styles/custom.css index 04331dd6ae0b..744934ae74e6 100644 --- a/packages/web/src/styles/custom.css +++ b/packages/web/src/styles/custom.css @@ -28,6 +28,32 @@ /* For the share component */ --sl-color-bg-surface: var(--sl-color-bg-nav); --sl-color-divider: var(--sl-color-gray-5); + + /* Fixed v2 announcement bar rendered below the header by PageFrame */ + --v2-banner-height: 2.75rem; +} + +:root[data-has-hero] { + --v2-banner-height: 0rem; +} + +/* Shift fixed chrome below the v2 banner */ +mobile-starlight-toc nav { + top: calc(var(--sl-nav-height) + var(--v2-banner-height) - 1px); +} + +html { + scroll-padding-top: calc(1.5rem + var(--sl-nav-height) + var(--sl-mobile-toc-height) + var(--v2-banner-height)); +} + +@media (min-width: 72rem) { + .right-sidebar { + padding-top: calc(var(--sl-nav-height) + var(--v2-banner-height)); + } + + html { + scroll-padding-top: calc(1.5rem + var(--sl-nav-height) + var(--v2-banner-height)); + } } body { From 7c22dbbaf9b8a056cd91f9186c895548c4ddfdee Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 19 Sep 2026 03:29:50 -0400 Subject: [PATCH 104/129] fix(web): use neutral colors for v2 banner --- packages/web/src/components/V2Banner.astro | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/web/src/components/V2Banner.astro b/packages/web/src/components/V2Banner.astro index 11ce915b6680..fa72e2a41292 100644 --- a/packages/web/src/components/V2Banner.astro +++ b/packages/web/src/components/V2Banner.astro @@ -6,11 +6,11 @@