Compare commits

..
Author SHA1 Message Date
Aiden Cline 4217091d02 feat: add retry logic 2026-04-23 13:41:29 -04:00
Aiden Cline f778c685f5 test: add additional test cases 2026-04-23 12:45:16 -04:00
opencode-agent[bot] 353532b1c1 chore: generate 2026-04-23 16:00:01 +00:00
Shoubhit DashandGitHub 9df7c78ebe fix(npm): respect npmrc for version lookups (#24016) 2026-04-23 21:28:54 +05:30
opencode eb7555d3c6 sync release versions for v1.14.22 2026-04-23 15:56:49 +00:00
opencode-agent[bot] 2cd89d64e9 chore: generate 2026-04-23 15:31:16 +00:00
Kit LangtonandGitHub 0517ab4695 refactor(session): migrate session domain to Effect Schema (#24005) 2026-04-23 11:30:02 -04:00
James LongandGitHub bbf67d0fff fix(tui): render all non-synthetic text parts of a user message (#24009) 2026-04-23 11:24:40 -04:00
Shoubhit DashandGitHub 38deb0f3ee fix(npm): respect npmrc config (#24001) 2026-04-23 19:54:01 +05:30
Simon KleeandGitHub 9b6db08d21 chore: add to TEAM_MEMBERS (#23975) 2026-04-23 13:02:40 +02:00
opencode-agent[bot] 3ae74cb047 chore: generate 2026-04-23 10:58:14 +00:00
Brendan AllanandGitHub 6002500bc0 feat(project): add icon_url_override field to projects (#23955) 2026-04-23 18:57:04 +08:00
Brendan AllanandGitHub 785f3589ab fix: add keyed prop to Show components for proper reactivity (#23935) 2026-04-23 17:32:01 +08:00
Frank a419f1c50f zen: hy3 preview 2026-04-23 02:56:41 -04:00
opencode 871789ce64 sync release versions for v1.14.21 2026-04-23 05:45:21 +00:00
Brendan AllanandGitHub df27baa00d refactor: remove redundant pending check from working memo (#23929) 2026-04-23 13:15:02 +08:00
75 changed files with 2821 additions and 699 deletions
+1
View File
@@ -13,3 +13,4 @@ R44VC0RP
rekram1-node
RhysSullivan
thdxr
simonklee
+16 -16
View File
@@ -29,7 +29,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -83,7 +83,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -117,7 +117,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -144,7 +144,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@ai-sdk/anthropic": "3.0.64",
"@ai-sdk/openai": "3.0.48",
@@ -168,7 +168,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -192,7 +192,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -225,7 +225,7 @@
},
"packages/desktop-electron": {
"name": "@opencode-ai/desktop-electron",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:",
@@ -269,7 +269,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@opencode-ai/shared": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -298,7 +298,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -314,7 +314,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.14.21",
"version": "1.14.22",
"bin": {
"opencode": "./bin/opencode",
},
@@ -459,7 +459,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"effect": "catalog:",
@@ -494,7 +494,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"cross-spawn": "catalog:",
},
@@ -509,7 +509,7 @@
},
"packages/shared": {
"name": "@opencode-ai/shared",
"version": "1.14.21",
"version": "1.14.22",
"bin": {
"opencode": "./bin/opencode",
},
@@ -533,7 +533,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -568,7 +568,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -617,7 +617,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.14.21",
"version": "1.14.22",
"description": "",
"type": "module",
"exports": {
+2 -33
View File
@@ -10,9 +10,8 @@ import type {
import { showToast } from "@opencode-ai/ui/toast"
import { getFilename } from "@opencode-ai/shared/util/path"
import { batch, createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js"
import { createStore, produce, reconcile, unwrap } from "solid-js/store"
import { createStore, produce, reconcile } from "solid-js/store"
import { useLanguage } from "@/context/language"
import { Persist, persisted } from "@/utils/persist"
import type { InitError } from "../pages/error"
import { useGlobalSDK } from "./global-sdk"
import { bootstrapDirectory, bootstrapGlobal, clearProviderRev } from "./global-sync/bootstrap"
@@ -24,7 +23,6 @@ import { estimateRootSessionTotal, loadRootSessionsWithFallback } from "./global
import { trimSessions } from "./global-sync/session-trim"
import type { ProjectMeta } from "./global-sync/types"
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
import { sanitizeProject } from "./global-sync/utils"
import { formatServerError } from "@/utils/server-errors"
import { queryOptions, skipToken, useQueryClient } from "@tanstack/solid-query"
@@ -56,15 +54,10 @@ function createGlobalSync() {
const sessionLoads = new Map<string, Promise<void>>()
const sessionMeta = new Map<string, { limit: number }>()
const [projectCache, setProjectCache, projectInit] = persisted(
Persist.global("globalSync.project", ["globalSync.project.v1"]),
createStore({ value: [] as Project[] }),
)
const [globalStore, setGlobalStore] = createStore<GlobalStore>({
ready: false,
path: { state: "", config: "", worktree: "", directory: "", home: "" },
project: projectCache.value,
project: [],
session_todo: {},
provider: { all: [], connected: [], default: {} },
provider_auth: {},
@@ -73,32 +66,18 @@ function createGlobalSync() {
})
const queryClient = useQueryClient()
let active = true
let projectWritten = false
let bootedAt = 0
let bootingRoot = false
let eventFrame: number | undefined
let eventTimer: ReturnType<typeof setTimeout> | undefined
onCleanup(() => {
active = false
})
onCleanup(() => {
if (eventFrame !== undefined) cancelAnimationFrame(eventFrame)
if (eventTimer !== undefined) clearTimeout(eventTimer)
})
const cacheProjects = () => {
setProjectCache(
"value",
untrack(() => globalStore.project.map(sanitizeProject)),
)
}
const setProjects = (next: Project[] | ((draft: Project[]) => Project[])) => {
projectWritten = true
setGlobalStore("project", next)
cacheProjects()
}
const setBootStore = ((...input: unknown[]) => {
@@ -117,16 +96,6 @@ function createGlobalSync() {
return (setGlobalStore as (...args: unknown[]) => unknown)(...input)
}) as typeof setGlobalStore
if (projectInit instanceof Promise) {
void projectInit.then(() => {
if (!active) return
if (projectWritten) return
const cached = projectCache.value
if (cached.length === 0) return
setGlobalStore("project", cached)
})
}
const setSessionTodo = (sessionID: string, todos: Todo[] | undefined) => {
if (!sessionID) return
if (!todos) {
@@ -156,13 +156,12 @@ export function createChildStoreManager(input: {
const init = () =>
createRoot((dispose) => {
const initialMeta = meta[0].value
const initialIcon = icon[0].value
const pathQuery = useQuery(() => loadPathQuery(directory))
const child = createStore<State>({
project: "",
projectMeta: initialMeta,
projectMeta: undefined,
icon: initialIcon,
provider_ready: false,
provider: { all: [], connected: [], default: {} },
@@ -208,11 +207,6 @@ export function createChildStoreManager(input: {
child[1]("vcs", (value) => value ?? cached)
})
onPersistedInit(meta[2], () => {
if (child[0].projectMeta !== initialMeta) return
child[1]("projectMeta", meta[0].value)
})
onPersistedInit(icon[2], () => {
if (child[0].icon !== initialIcon) return
child[1]("icon", icon[0].value)
+2 -32
View File
@@ -391,37 +391,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
? globalSync.data.project.find((x) => x.id === projectID)
: globalSync.data.project.find((x) => x.worktree === project.worktree)
const local = childStore.projectMeta
const localOverride =
local?.name !== undefined ||
local?.commands?.start !== undefined ||
local?.icon?.override !== undefined ||
local?.icon?.color !== undefined
const base = {
...metadata,
...project,
icon: {
url: metadata?.icon?.url,
override: metadata?.icon?.override ?? childStore.icon,
color: metadata?.icon?.color,
},
}
const isGlobal = projectID === "global" || (metadata?.id === undefined && localOverride)
if (!isGlobal) return base
return {
...base,
id: base.id ?? "global",
name: local?.name,
commands: local?.commands,
icon: {
url: base.icon?.url,
override: local?.icon?.override,
color: local?.icon?.color,
},
}
return { ...metadata, ...project }
}
const roots = createMemo(() => {
@@ -516,7 +486,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
}
for (const project of projects) {
if (project.icon?.color || project.icon.url) continue
if (project.icon?.color || project.icon?.override || project.icon?.url) continue
const worktree = project.worktree
const existing = colors[worktree]
const color = existing ?? pickAvailableColor(used)
@@ -22,9 +22,7 @@ const OPENCODE_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750"
export function getProjectAvatarSource(id?: string, icon?: { color?: string; url?: string; override?: string }) {
return id === OPENCODE_PROJECT_ID
? "https://opencode.ai/favicon.svg"
: icon?.color
? undefined
: icon?.override || icon?.url
: (icon?.override ?? (icon?.color ? undefined : icon?.url))
}
export const ProjectIcon = (props: { project: LocalProject; class?: string; notify?: boolean }): JSX.Element => {
@@ -269,10 +267,10 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
</Show>
</div>
</div>
<Show when={currentChild()}>
<Show when={currentChild()} keyed>
{(child) => (
<div class="w-full">
<SessionItem {...props} session={child()} level={(props.level ?? 0) + 1} />
<SessionItem {...props} session={child} level={(props.level ?? 0) + 1} />
</div>
)}
</Show>
@@ -259,7 +259,7 @@ export function MessageTimeline(props: {
if (!id) return idle
return sync.data.session_status[id] ?? idle
})
const working = createMemo(() => !!pending() || sessionStatus().type !== "idle")
const working = createMemo(() => sessionStatus().type !== "idle")
const tint = createMemo(() => messageAgentColor(sessionMessages(), sync.data.agent))
const [timeoutDone, setTimeoutDone] = createSignal(true)
@@ -812,7 +812,7 @@ export function MessageTimeline(props: {
</Show>
</div>
</div>
<Show when={sessionID()}>
<Show when={sessionID()} keyed>
{(id) => (
<div class="shrink-0 flex items-center gap-3">
<SessionContextUsage placement="bottom" />
@@ -878,12 +878,12 @@ export function MessageTimeline(props: {
</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</Show>
<DropdownMenu.Item onSelect={() => void archiveSession(id())}>
<DropdownMenu.Item onSelect={() => void archiveSession(id)}>
<DropdownMenu.ItemLabel>{language.t("common.archive")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item
onSelect={() => dialog.show(() => <DialogDeleteSession sessionID={id()} />)}
onSelect={() => dialog.show(() => <DialogDeleteSession sessionID={id} />)}
>
<DropdownMenu.ItemLabel>{language.t("common.delete")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.14.21",
"version": "1.14.22",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.14.21",
"version": "1.14.22",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.14.21",
"version": "1.14.22",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.14.21",
"version": "1.14.22",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop-electron",
"private": true,
"version": "1.14.21",
"version": "1.14.22",
"type": "module",
"license": "MIT",
"homepage": "https://opencode.ai",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
"version": "1.14.21",
"version": "1.14.22",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.14.21",
"version": "1.14.22",
"private": true,
"type": "module",
"license": "MIT",
+6 -6
View File
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
version = "1.14.21"
version = "1.14.22"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/anomalyco/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/opencode-darwin-arm64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/opencode-darwin-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/opencode-linux-arm64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/opencode-linux-x64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.21/opencode-windows-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.22/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.14.21",
"version": "1.14.22",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
@@ -0,0 +1,2 @@
ALTER TABLE `project` ADD `icon_url_override` text;
UPDATE `project` SET `icon_url_override` = `icon_url` WHERE `icon_url` IS NOT NULL;
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.14.21",
"version": "1.14.22",
"name": "opencode",
"type": "module",
"license": "MIT",
+12 -9
View File
@@ -159,6 +159,7 @@ Schema at source.
These are the highest-priority next targets. Each is a small, self-contained
schema module with a clear domain.
- [x] `src/account/schema.ts`
- [x] `src/control-plane/schema.ts`
- [x] `src/permission/schema.ts`
- [x] `src/project/schema.ts`
@@ -166,8 +167,10 @@ schema module with a clear domain.
- [x] `src/pty/schema.ts`
- [x] `src/question/schema.ts`
- [x] `src/session/schema.ts`
- [x] `src/storage/schema.ts`
- [x] `src/sync/schema.ts`
- [x] `src/tool/schema.ts`
- [x] `src/util/schema.ts`
### Session domain
@@ -248,15 +251,15 @@ Possible later tightening after the Schema-first migration is stable:
- promote repeated opaque strings and timestamp numbers into branded/newtype
leaf schemas where that adds domain value without changing the wire format
- [ ] `src/session/compaction.ts`
- [ ] `src/session/message-v2.ts`
- [ ] `src/session/message.ts`
- [ ] `src/session/prompt.ts`
- [ ] `src/session/revert.ts`
- [ ] `src/session/session.ts`
- [ ] `src/session/status.ts`
- [ ] `src/session/summary.ts`
- [ ] `src/session/todo.ts`
- [x] `src/session/compaction.ts`
- [x] `src/session/message-v2.ts`
- [x] `src/session/message.ts`
- [x] `src/session/prompt.ts`
- [x] `src/session/revert.ts`
- [x] `src/session/session.ts`
- [x] `src/session/status.ts`
- [x] `src/session/summary.ts`
- [x] `src/session/todo.ts`
### Provider domain
+2 -2
View File
@@ -372,7 +372,7 @@ export class Agent implements ACPAgent {
}
if (part.tool === "todowrite") {
const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output))
const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output))
if (parsedTodos.success) {
await this.connection
.sessionUpdate({
@@ -901,7 +901,7 @@ export class Agent implements ACPAgent {
}
if (part.tool === "todowrite") {
const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output))
const parsedTodos = z.array(Todo.Info.zod).safeParse(JSON.parse(part.state.output))
if (parsedTodos.success) {
await this.connection
.sessionUpdate({
+3 -2
View File
@@ -11,6 +11,7 @@ import { ShareNext } from "../../share"
import { EOL } from "os"
import { Filesystem } from "../../util"
import { AppRuntime } from "@/effect/app-runtime"
import { Schema } from "effect"
/** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */
export type ShareData =
@@ -154,10 +155,10 @@ export const ImportCommand = cmd({
return
}
const info = Session.Info.parse({
const info = Schema.decodeUnknownSync(Session.Info)({
...exportData.info,
projectID: Instance.project.id,
})
}) as Session.Info
const row = Session.toRow(info)
Database.use((db) =>
db
@@ -1250,7 +1250,17 @@ function UserMessage(props: {
}) {
const ctx = use()
const local = useLocal()
const text = createMemo(() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0])
const text = createMemo(() => {
const texts = props.parts
.map((x) => {
if (x.type === "text" && !x.synthetic) {
return x.text
}
return null
})
.filter(Boolean)
return texts.join("\n\n")
})
const files = createMemo(() => props.parts.flatMap((x) => (x.type === "file" ? [x] : [])))
const { theme } = useTheme()
const [hover, setHover] = createSignal(false)
@@ -1285,7 +1295,7 @@ function UserMessage(props: {
backgroundColor={hover() ? theme.backgroundElement : theme.backgroundPanel}
flexShrink={0}
>
<text fg={theme.text}>{text()?.text}</text>
<text fg={theme.text}>{text()}</text>
<Show when={files().length}>
<box flexDirection="row" paddingBottom={metadataVisible() ? 1 : 0} paddingTop={1} gap={1} flexWrap="wrap">
<For each={files()}>
+12 -9
View File
@@ -132,6 +132,17 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | ChildPro
Effect.catch(() => Effect.succeed({ code: ChildProcessSpawner.ExitCode(1), stdout: "", stderr: "" })),
)
const viewVersion = Effect.fnUntraced(function* (method: "npm" | "pnpm" | "bun", spec: string) {
const args = method === "bun" ? ["pm", "view", spec, "version", "--json"] : ["view", spec, "version", "--json"]
const result = yield* run([method, ...args])
if (result.code !== 0 || !result.stdout.trim()) {
return yield* new UpgradeFailedError({
stderr: result.stderr || result.stdout || `Failed to resolve ${spec}`,
})
}
return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.String))(result.stdout)
})
const getBrewFormula = Effect.fnUntraced(function* () {
const tapFormula = yield* text(["brew", "list", "--formula", "anomalyco/tap/opencode"])
if (tapFormula.includes("opencode")) return "anomalyco/tap/opencode"
@@ -217,15 +228,7 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | ChildPro
}
if (detectedMethod === "npm" || detectedMethod === "bun" || detectedMethod === "pnpm") {
const r = (yield* text(["npm", "config", "get", "registry"])).trim()
const reg = r || "https://registry.npmjs.org"
const registry = reg.endsWith("/") ? reg.slice(0, -1) : reg
const channel = InstallationChannel
const response = yield* httpOk.execute(
HttpClientRequest.get(`${registry}/opencode-ai/${channel}`).pipe(HttpClientRequest.acceptJson),
)
const data = yield* HttpClientResponse.schemaBodyJson(NpmPackage)(response)
return data.version
return yield* viewVersion(detectedMethod, `opencode-ai@${InstallationChannel}`)
}
if (detectedMethod === "choco") {
+70 -21
View File
@@ -1,14 +1,19 @@
export * as Npm from "."
import path from "path"
import { fileURLToPath } from "url"
import npa from "npm-package-arg"
import semver from "semver"
import { Effect, Schema, Context, Layer, Option, FileSystem } from "effect"
import Config from "@npmcli/config"
import { definitions, flatten, nerfDarts, shorthands } from "@npmcli/config/lib/definitions/index.js"
import { Effect, Schema, Context, Layer, Option, FileSystem, Stream } from "effect"
import { NodeFileSystem } from "@effect/platform-node"
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Global } from "@opencode-ai/shared/global"
import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import * as CrossSpawnSpawner from "../effect/cross-spawn-spawner"
import { makeRuntime } from "../effect/runtime"
export class InstallFailedError extends Schema.TaggedErrorClass<InstallFailedError>()("NpmInstallFailedError", {
@@ -40,12 +45,39 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/Npm") {}
const illegal = process.platform === "win32" ? new Set(["<", ">", ":", '"', "|", "?", "*"]) : undefined
const npmPath = fileURLToPath(new URL("../..", import.meta.url))
export function sanitize(pkg: string) {
if (!illegal) return pkg
return Array.from(pkg, (char) => (illegal.has(char) || char.charCodeAt(0) < 32 ? "_" : char)).join("")
}
const loadOptions = (dir: string) =>
Effect.tryPromise({
try: async () => {
const config = new Config({
npmPath,
cwd: dir,
env: { ...process.env },
argv: [process.execPath, process.execPath],
execPath: process.execPath,
platform: process.platform,
definitions,
flatten,
nerfDarts,
shorthands,
warn: false,
})
await config.load()
return config.flat
},
catch: (cause) =>
new InstallFailedError({
cause,
dir,
}),
})
const resolveEntryPoint = (name: string, dir: string): EntryPoint => {
let entrypoint: Option.Option<string>
try {
@@ -76,12 +108,41 @@ export const layer = Layer.effect(
const global = yield* Global.Service
const fs = yield* FileSystem.FileSystem
const flock = yield* EffectFlock.Service
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
const directory = (pkg: string) => path.join(global.cache, "packages", sanitize(pkg))
const runView = Effect.fnUntraced(function* (cmd: string[]) {
const handle = yield* spawner.spawn(
ChildProcess.make(cmd[0], cmd.slice(1), {
extendEnv: true,
}),
)
const [stdout, stderr] = yield* Effect.all(
[Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))],
{ concurrency: 2 },
)
const code = yield* handle.exitCode
if (code !== 0 || !stdout.trim()) {
return yield* Effect.fail(stderr || stdout || `Failed to run ${cmd.join(" ")}`)
}
return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.String))(stdout)
}, Effect.scoped)
const viewLatestVersion = Effect.fnUntraced(function* (pkg: string) {
return yield* runView(["npm", "view", pkg, "dist-tags.latest", "--json"]).pipe(
Effect.catch(() =>
runView(["pnpm", "view", pkg, "dist-tags.latest", "--json"]).pipe(
Effect.catch(() => runView(["bun", "pm", "view", pkg, "dist-tags.latest", "--json"])),
),
),
)
})
const reify = (input: { dir: string; add?: string[] }) =>
Effect.gen(function* () {
yield* flock.acquire(`npm-install:${input.dir}`)
const { Arborist } = yield* Effect.promise(() => import("@npmcli/arborist"))
const add = input.add ?? []
const npmOptions = yield* loadOptions(input.dir)
const arborist = new Arborist({
...npmOptions,
path: input.dir,
binLinks: true,
progress: false,
@@ -91,14 +152,15 @@ export const layer = Layer.effect(
return yield* Effect.tryPromise({
try: () =>
arborist.reify({
add: input?.add || [],
...npmOptions,
add,
save: true,
saveType: "prod",
}),
catch: (cause) =>
new InstallFailedError({
cause,
add: input?.add,
add,
dir: input.dir,
}),
}) as Effect.Effect<ArboristTree, InstallFailedError>
@@ -109,29 +171,15 @@ export const layer = Layer.effect(
)
const outdated = Effect.fn("Npm.outdated")(function* (pkg: string, cachedVersion: string) {
const response = yield* Effect.tryPromise({
try: () => fetch(`https://registry.npmjs.org/${pkg}`),
catch: () => undefined,
}).pipe(Effect.orElseSucceed(() => undefined))
if (!response || !response.ok) {
return false
}
const data = yield* Effect.tryPromise({
try: () => response.json() as Promise<{ "dist-tags"?: { latest?: string } }>,
catch: () => undefined,
}).pipe(Effect.orElseSucceed(() => undefined))
const latestVersion = data?.["dist-tags"]?.latest
if (!latestVersion) {
const latestVersion = yield* viewLatestVersion(pkg).pipe(Effect.option)
if (Option.isNone(latestVersion)) {
return false
}
const range = /[\s^~*xX<>|=]/.test(cachedVersion)
if (range) return !semver.satisfies(latestVersion, cachedVersion)
if (range) return !semver.satisfies(latestVersion.value, cachedVersion)
return semver.lt(cachedVersion, latestVersion)
return semver.lt(cachedVersion, latestVersion.value)
})
const add = Effect.fn("Npm.add")(function* (pkg: string) {
@@ -270,6 +318,7 @@ export const defaultLayer = layer.pipe(
Layer.provide(AppFileSystem.layer),
Layer.provide(Global.layer),
Layer.provide(NodeFileSystem.layer),
Layer.provide(CrossSpawnSpawner.defaultLayer),
)
const { runPromise } = makeRuntime(Service, defaultLayer)
@@ -8,6 +8,7 @@ export const ProjectTable = sqliteTable("project", {
vcs: text(),
name: text(),
icon_url: text(),
icon_url_override: text(),
icon_color: text(),
...Timestamps,
time_initialized: integer(),
+10 -1
View File
@@ -60,7 +60,13 @@ type Row = typeof ProjectTable.$inferSelect
export function fromRow(row: Row): Info {
const icon =
row.icon_url || row.icon_color ? { url: row.icon_url ?? undefined, color: row.icon_color ?? undefined } : undefined
row.icon_url || row.icon_url_override || row.icon_color
? {
url: row.icon_url ?? undefined,
override: row.icon_url_override ?? undefined,
color: row.icon_color ?? undefined,
}
: undefined
return {
id: row.id,
worktree: row.worktree,
@@ -289,6 +295,7 @@ export const layer: Layer.Layer<
vcs: result.vcs ?? null,
name: result.name,
icon_url: result.icon?.url,
icon_url_override: result.icon?.override,
icon_color: result.icon?.color,
time_created: result.time.created,
time_updated: result.time.updated,
@@ -303,6 +310,7 @@ export const layer: Layer.Layer<
vcs: result.vcs ?? null,
name: result.name,
icon_url: result.icon?.url,
icon_url_override: result.icon?.override,
icon_color: result.icon?.color,
time_updated: result.time.updated,
time_initialized: result.time.initialized,
@@ -365,6 +373,7 @@ export const layer: Layer.Layer<
.set({
name: input.name,
icon_url: input.icon?.url,
icon_url_override: input.icon?.override,
icon_color: input.icon?.color,
commands: input.commands,
time_updated: Date.now(),
@@ -335,7 +335,7 @@ export const ExperimentalRoutes = lazy(() =>
description: "List of sessions",
content: {
"application/json": {
schema: resolver(Session.GlobalInfo.array()),
schema: resolver(Session.GlobalInfo.zod.array()),
},
},
},
@@ -23,6 +23,7 @@ import { PermissionID } from "@/permission/schema"
import { ModelID, ProviderID } from "@/provider/schema"
import { errors } from "../../error"
import { lazy } from "@/util/lazy"
import { zodObject } from "@/util/effect-zod"
import { Bus } from "@/bus"
import { NamedError } from "@opencode-ai/shared/util/error"
import { jsonRequest, runRequest } from "./trace"
@@ -42,7 +43,7 @@ export const SessionRoutes = lazy(() =>
description: "List of sessions",
content: {
"application/json": {
schema: resolver(Session.Info.array()),
schema: resolver(Session.Info.zod.array()),
},
},
},
@@ -87,7 +88,7 @@ export const SessionRoutes = lazy(() =>
description: "Get session status",
content: {
"application/json": {
schema: resolver(z.record(z.string(), SessionStatus.Info)),
schema: resolver(z.record(z.string(), SessionStatus.Info.zod)),
},
},
},
@@ -112,7 +113,7 @@ export const SessionRoutes = lazy(() =>
description: "Get session",
content: {
"application/json": {
schema: resolver(Session.Info),
schema: resolver(Session.Info.zod),
},
},
},
@@ -122,7 +123,7 @@ export const SessionRoutes = lazy(() =>
validator(
"param",
z.object({
sessionID: Session.GetInput,
sessionID: Session.GetInput.zod,
}),
),
async (c) => {
@@ -145,7 +146,7 @@ export const SessionRoutes = lazy(() =>
description: "List of children",
content: {
"application/json": {
schema: resolver(Session.Info.array()),
schema: resolver(Session.Info.zod.array()),
},
},
},
@@ -155,7 +156,7 @@ export const SessionRoutes = lazy(() =>
validator(
"param",
z.object({
sessionID: Session.ChildrenInput,
sessionID: Session.ChildrenInput.zod,
}),
),
async (c) => {
@@ -177,7 +178,7 @@ export const SessionRoutes = lazy(() =>
description: "Todo list",
content: {
"application/json": {
schema: resolver(Todo.Info.array()),
schema: resolver(Todo.Info.zod.array()),
},
},
},
@@ -210,13 +211,13 @@ export const SessionRoutes = lazy(() =>
description: "Successfully created session",
content: {
"application/json": {
schema: resolver(Session.Info),
schema: resolver(Session.Info.zod),
},
},
},
},
}),
validator("json", Session.CreateInput),
validator("json", Session.CreateInput.zod),
async (c) =>
jsonRequest("SessionRoutes.create", c, function* () {
const body = c.req.valid("json") ?? {}
@@ -245,7 +246,7 @@ export const SessionRoutes = lazy(() =>
validator(
"param",
z.object({
sessionID: Session.RemoveInput,
sessionID: Session.RemoveInput.zod,
}),
),
async (c) =>
@@ -267,7 +268,7 @@ export const SessionRoutes = lazy(() =>
description: "Successfully updated session",
content: {
"application/json": {
schema: resolver(Session.Info),
schema: resolver(Session.Info.zod),
},
},
},
@@ -375,7 +376,7 @@ export const SessionRoutes = lazy(() =>
description: "200",
content: {
"application/json": {
schema: resolver(Session.Info),
schema: resolver(Session.Info.zod),
},
},
},
@@ -384,14 +385,14 @@ export const SessionRoutes = lazy(() =>
validator(
"param",
z.object({
sessionID: Session.ForkInput.shape.sessionID,
sessionID: SessionID.zod,
}),
),
validator("json", Session.ForkInput.omit({ sessionID: true })),
validator("json", zodObject(Session.ForkInput).omit({ sessionID: true })),
async (c) =>
jsonRequest("SessionRoutes.fork", c, function* () {
const sessionID = c.req.valid("param").sessionID
const body = c.req.valid("json")
const body = c.req.valid("json") as { messageID?: MessageID }
const svc = yield* Session.Service
return yield* svc.fork({ ...body, sessionID })
}),
@@ -438,7 +439,7 @@ export const SessionRoutes = lazy(() =>
description: "Successfully shared session",
content: {
"application/json": {
schema: resolver(Session.Info),
schema: resolver(Session.Info.zod),
},
},
},
@@ -480,18 +481,13 @@ export const SessionRoutes = lazy(() =>
validator(
"param",
z.object({
sessionID: SessionSummary.DiffInput.shape.sessionID,
}),
),
validator(
"query",
z.object({
messageID: SessionSummary.DiffInput.shape.messageID,
sessionID: SessionID.zod,
}),
),
validator("query", zodObject(SessionSummary.DiffInput).omit({ sessionID: true })),
async (c) =>
jsonRequest("SessionRoutes.diff", c, function* () {
const query = c.req.valid("query")
const query = c.req.valid("query") as Omit<SessionSummary.DiffInput, "sessionID">
const params = c.req.valid("param")
const summary = yield* SessionSummary.Service
return yield* summary.diff({
@@ -511,7 +507,7 @@ export const SessionRoutes = lazy(() =>
description: "Successfully unshared session",
content: {
"application/json": {
schema: resolver(Session.Info),
schema: resolver(Session.Info.zod),
},
},
},
@@ -872,7 +868,7 @@ export const SessionRoutes = lazy(() =>
sessionID: SessionID.zod,
}),
),
validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })),
validator("json", zodObject(SessionPrompt.PromptInput).omit({ sessionID: true })),
async (c) => {
c.status(200)
c.header("Content-Type", "application/json")
@@ -910,7 +906,7 @@ export const SessionRoutes = lazy(() =>
sessionID: SessionID.zod,
}),
),
validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })),
validator("json", zodObject(SessionPrompt.PromptInput).omit({ sessionID: true })),
async (c) => {
const sessionID = c.req.valid("param").sessionID
const body = c.req.valid("json")
@@ -960,11 +956,11 @@ export const SessionRoutes = lazy(() =>
sessionID: SessionID.zod,
}),
),
validator("json", SessionPrompt.CommandInput.omit({ sessionID: true })),
validator("json", zodObject(SessionPrompt.CommandInput).omit({ sessionID: true })),
async (c) =>
jsonRequest("SessionRoutes.command", c, function* () {
const sessionID = c.req.valid("param").sessionID
const body = c.req.valid("json")
const body = c.req.valid("json") as Omit<SessionPrompt.CommandInput, "sessionID">
const svc = yield* SessionPrompt.Service
return yield* svc.command({ ...body, sessionID })
}),
@@ -993,11 +989,11 @@ export const SessionRoutes = lazy(() =>
sessionID: SessionID.zod,
}),
),
validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })),
validator("json", zodObject(SessionPrompt.ShellInput).omit({ sessionID: true })),
async (c) =>
jsonRequest("SessionRoutes.shell", c, function* () {
const sessionID = c.req.valid("param").sessionID
const body = c.req.valid("json")
const body = c.req.valid("json") as Omit<SessionPrompt.ShellInput, "sessionID">
const svc = yield* SessionPrompt.Service
return yield* svc.shell({ ...body, sessionID })
}),
@@ -1013,7 +1009,7 @@ export const SessionRoutes = lazy(() =>
description: "Updated session",
content: {
"application/json": {
schema: resolver(Session.Info),
schema: resolver(Session.Info.zod),
},
},
},
@@ -1026,16 +1022,14 @@ export const SessionRoutes = lazy(() =>
sessionID: SessionID.zod,
}),
),
validator("json", SessionRevert.RevertInput.omit({ sessionID: true })),
validator("json", zodObject(SessionRevert.RevertInput).omit({ sessionID: true })),
async (c) => {
const sessionID = c.req.valid("param").sessionID
log.info("revert", c.req.valid("json"))
const body = c.req.valid("json") as Omit<SessionRevert.RevertInput, "sessionID">
log.info("revert", body)
return jsonRequest("SessionRoutes.revert", c, function* () {
const svc = yield* SessionRevert.Service
return yield* svc.revert({
sessionID,
...c.req.valid("json"),
})
return yield* svc.revert({ sessionID, ...body })
})
},
)
@@ -1050,7 +1044,7 @@ export const SessionRoutes = lazy(() =>
description: "Updated session",
content: {
"application/json": {
schema: resolver(Session.Info),
schema: resolver(Session.Info.zod),
},
},
},
+177 -176
View File
@@ -1,191 +1,192 @@
import z from "zod"
import { Schema } from "effect"
import { SessionID } from "./schema"
import { ModelID, ProviderID } from "../provider/schema"
import { NamedError } from "@opencode-ai/shared/util/error"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { namedSchemaError } from "@/util/named-schema-error"
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
export const AuthError = NamedError.create(
"ProviderAuthError",
z.object({
providerID: z.string(),
message: z.string(),
}),
)
export const ToolCall = z
.object({
state: z.literal("call"),
step: z.number().optional(),
toolCallId: z.string(),
toolName: z.string(),
args: z.custom<Required<unknown>>(),
})
.meta({
ref: "ToolCall",
})
export type ToolCall = z.infer<typeof ToolCall>
export const ToolPartialCall = z
.object({
state: z.literal("partial-call"),
step: z.number().optional(),
toolCallId: z.string(),
toolName: z.string(),
args: z.custom<Required<unknown>>(),
})
.meta({
ref: "ToolPartialCall",
})
export type ToolPartialCall = z.infer<typeof ToolPartialCall>
export const ToolResult = z
.object({
state: z.literal("result"),
step: z.number().optional(),
toolCallId: z.string(),
toolName: z.string(),
args: z.custom<Required<unknown>>(),
result: z.string(),
})
.meta({
ref: "ToolResult",
})
export type ToolResult = z.infer<typeof ToolResult>
export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).meta({
ref: "ToolInvocation",
export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {})
export const AuthError = namedSchemaError("ProviderAuthError", {
providerID: Schema.String,
message: Schema.String,
})
export type ToolInvocation = z.infer<typeof ToolInvocation>
export const TextPart = z
.object({
type: z.literal("text"),
text: z.string(),
})
.meta({
ref: "TextPart",
})
export type TextPart = z.infer<typeof TextPart>
const AuthErrorEffect = Schema.Struct({
name: Schema.Literal("ProviderAuthError"),
data: Schema.Struct({
providerID: Schema.String,
message: Schema.String,
}),
})
export const ReasoningPart = z
.object({
type: z.literal("reasoning"),
text: z.string(),
providerMetadata: z.record(z.string(), z.any()).optional(),
})
.meta({
ref: "ReasoningPart",
})
export type ReasoningPart = z.infer<typeof ReasoningPart>
const OutputLengthErrorEffect = Schema.Struct({
name: Schema.Literal("MessageOutputLengthError"),
data: Schema.Struct({}),
})
export const ToolInvocationPart = z
.object({
type: z.literal("tool-invocation"),
toolInvocation: ToolInvocation,
})
.meta({
ref: "ToolInvocationPart",
})
export type ToolInvocationPart = z.infer<typeof ToolInvocationPart>
const UnknownErrorEffect = Schema.Struct({
name: Schema.Literal("UnknownError"),
data: Schema.Struct({
message: Schema.String,
}),
})
export const SourceUrlPart = z
.object({
type: z.literal("source-url"),
sourceId: z.string(),
url: z.string(),
title: z.string().optional(),
providerMetadata: z.record(z.string(), z.any()).optional(),
})
.meta({
ref: "SourceUrlPart",
})
export type SourceUrlPart = z.infer<typeof SourceUrlPart>
export const ToolCall = Schema.Struct({
state: Schema.Literal("call"),
step: Schema.optional(Schema.Number),
toolCallId: Schema.String,
toolName: Schema.String,
args: Schema.Unknown,
})
.annotate({ identifier: "ToolCall" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ToolCall = Schema.Schema.Type<typeof ToolCall>
export const FilePart = z
.object({
type: z.literal("file"),
mediaType: z.string(),
filename: z.string().optional(),
url: z.string(),
})
.meta({
ref: "FilePart",
})
export type FilePart = z.infer<typeof FilePart>
export const ToolPartialCall = Schema.Struct({
state: Schema.Literal("partial-call"),
step: Schema.optional(Schema.Number),
toolCallId: Schema.String,
toolName: Schema.String,
args: Schema.Unknown,
})
.annotate({ identifier: "ToolPartialCall" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ToolPartialCall = Schema.Schema.Type<typeof ToolPartialCall>
export const StepStartPart = z
.object({
type: z.literal("step-start"),
})
.meta({
ref: "StepStartPart",
})
export type StepStartPart = z.infer<typeof StepStartPart>
export const ToolResult = Schema.Struct({
state: Schema.Literal("result"),
step: Schema.optional(Schema.Number),
toolCallId: Schema.String,
toolName: Schema.String,
args: Schema.Unknown,
result: Schema.String,
})
.annotate({ identifier: "ToolResult" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ToolResult = Schema.Schema.Type<typeof ToolResult>
export const MessagePart = z
.discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart])
.meta({
ref: "MessagePart",
})
export type MessagePart = z.infer<typeof MessagePart>
export const ToolInvocation = Schema.Union([ToolCall, ToolPartialCall, ToolResult])
.annotate({ identifier: "ToolInvocation", discriminator: "state" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ToolInvocation = Schema.Schema.Type<typeof ToolInvocation>
export const Info = z
.object({
id: z.string(),
role: z.enum(["user", "assistant"]),
parts: z.array(MessagePart),
metadata: z
.object({
time: z.object({
created: z.number(),
completed: z.number().optional(),
export const TextPart = Schema.Struct({
type: Schema.Literal("text"),
text: Schema.String,
})
.annotate({ identifier: "TextPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type TextPart = Schema.Schema.Type<typeof TextPart>
export const ReasoningPart = Schema.Struct({
type: Schema.Literal("reasoning"),
text: Schema.String,
providerMetadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
})
.annotate({ identifier: "ReasoningPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ReasoningPart = Schema.Schema.Type<typeof ReasoningPart>
export const ToolInvocationPart = Schema.Struct({
type: Schema.Literal("tool-invocation"),
toolInvocation: ToolInvocation,
})
.annotate({ identifier: "ToolInvocationPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ToolInvocationPart = Schema.Schema.Type<typeof ToolInvocationPart>
export const SourceUrlPart = Schema.Struct({
type: Schema.Literal("source-url"),
sourceId: Schema.String,
url: Schema.String,
title: Schema.optional(Schema.String),
providerMetadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
})
.annotate({ identifier: "SourceUrlPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type SourceUrlPart = Schema.Schema.Type<typeof SourceUrlPart>
export const FilePart = Schema.Struct({
type: Schema.Literal("file"),
mediaType: Schema.String,
filename: Schema.optional(Schema.String),
url: Schema.String,
})
.annotate({ identifier: "FilePart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type FilePart = Schema.Schema.Type<typeof FilePart>
export const StepStartPart = Schema.Struct({
type: Schema.Literal("step-start"),
})
.annotate({ identifier: "StepStartPart" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type StepStartPart = Schema.Schema.Type<typeof StepStartPart>
export const MessagePart = Schema.Union([
TextPart,
ReasoningPart,
ToolInvocationPart,
SourceUrlPart,
FilePart,
StepStartPart,
])
.annotate({ identifier: "MessagePart", discriminator: "type" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type MessagePart = Schema.Schema.Type<typeof MessagePart>
export const Info = Schema.Struct({
id: Schema.String,
role: Schema.Literals(["user", "assistant"]),
parts: Schema.Array(MessagePart),
metadata: Schema.Struct({
time: Schema.Struct({
created: Schema.Number,
completed: Schema.optional(Schema.Number),
}),
error: Schema.optional(Schema.Union([AuthErrorEffect, UnknownErrorEffect, OutputLengthErrorEffect])),
sessionID: SessionID,
tool: Schema.Record(
Schema.String,
Schema.StructWithRest(
Schema.Struct({
title: Schema.String,
snapshot: Schema.optional(Schema.String),
time: Schema.Struct({
start: Schema.Number,
end: Schema.Number,
}),
}),
error: z
.discriminatedUnion("name", [AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema])
.optional(),
sessionID: SessionID.zod,
tool: z.record(
z.string(),
z
.object({
title: z.string(),
snapshot: z.string().optional(),
time: z.object({
start: z.number(),
end: z.number(),
}),
})
.catchall(z.any()),
),
assistant: z
.object({
system: z.string().array(),
modelID: ModelID.zod,
providerID: ProviderID.zod,
path: z.object({
cwd: z.string(),
root: z.string(),
}),
cost: z.number(),
summary: z.boolean().optional(),
tokens: z.object({
input: z.number(),
output: z.number(),
reasoning: z.number(),
cache: z.object({
read: z.number(),
write: z.number(),
}),
}),
})
.optional(),
snapshot: z.string().optional(),
})
.meta({ ref: "MessageMetadata" }),
})
.meta({
ref: "Message",
})
export type Info = z.infer<typeof Info>
[Schema.Record(Schema.String, Schema.Unknown)],
),
),
assistant: Schema.optional(
Schema.Struct({
system: Schema.Array(Schema.String),
modelID: ModelID,
providerID: ProviderID,
path: Schema.Struct({
cwd: Schema.String,
root: Schema.String,
}),
cost: Schema.Number,
summary: Schema.optional(Schema.Boolean),
tokens: Schema.Struct({
input: Schema.Number,
output: Schema.Number,
reasoning: Schema.Number,
cache: Schema.Struct({
read: Schema.Number,
write: Schema.Number,
}),
}),
}),
),
snapshot: Schema.optional(Schema.String),
}).annotate({ identifier: "MessageMetadata" }),
})
.annotate({ identifier: "Message" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type Info = Schema.Schema.Type<typeof Info>
export * as Message from "./message"
+3 -1
View File
@@ -62,7 +62,9 @@ export function toPartialRow(info: DeepPartial<Session.Info>) {
export default [
SyncEvent.project(Session.Event.Created, (db, data) => {
db.insert(SessionTable).values(Session.toRow(data.info)).run()
db.insert(SessionTable)
.values(Session.toRow(data.info as Session.Info))
.run()
}),
SyncEvent.project(Session.Event.Updated, (db, data) => {
+74 -75
View File
@@ -43,7 +43,9 @@ import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Truncate } from "@/tool"
import { decodeDataUrl } from "@/util/data-url"
import { Process } from "@/util"
import { Cause, Effect, Exit, Layer, Option, Scope, Context } from "effect"
import { Cause, Effect, Exit, Layer, Option, Scope, Context, Schema } from "effect"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { EffectLogger } from "@/effect"
import { InstanceState } from "@/effect"
import { TaskTool, type TaskPromptOps } from "@/tool/task"
@@ -69,7 +71,7 @@ const elog = EffectLogger.create({ service: "session.prompt" })
export interface Interface {
readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
readonly prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts>
readonly loop: (input: z.infer<typeof LoopInput>) => Effect.Effect<MessageV2.WithParts>
readonly loop: (input: LoopInput) => Effect.Effect<MessageV2.WithParts>
readonly shell: (input: ShellInput) => Effect.Effect<MessageV2.WithParts>
readonly command: (input: CommandInput) => Effect.Effect<MessageV2.WithParts>
readonly resolvePromptParts: (template: string) => Effect.Effect<PromptInput["parts"]>
@@ -1532,9 +1534,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the
},
)
const loop: (input: z.infer<typeof LoopInput>) => Effect.Effect<MessageV2.WithParts> = Effect.fn(
"SessionPrompt.loop",
)(function* (input: z.infer<typeof LoopInput>) {
const loop: (input: LoopInput) => Effect.Effect<MessageV2.WithParts> = Effect.fn("SessionPrompt.loop")(function* (
input: LoopInput,
) {
return yield* state.ensureRunning(input.sessionID, lastAssistant(input.sessionID), runLoop(input.sessionID))
})
@@ -1701,91 +1703,88 @@ export const defaultLayer = Layer.suspend(() =>
),
),
)
export const PromptInput = z.object({
sessionID: SessionID.zod,
messageID: MessageID.zod.optional(),
model: z
.object({
providerID: ProviderID.zod,
modelID: ModelID.zod,
})
.optional(),
agent: z.string().optional(),
noReply: z.boolean().optional(),
tools: z
.record(z.string(), z.boolean())
.optional()
.describe("@deprecated tools and permissions have been merged, you can set permissions on the session itself now"),
format: MessageV2.Format.zod.optional(),
system: z.string().optional(),
variant: z.string().optional(),
parts: z.array(
z.discriminatedUnion("type", [
MessageV2.TextPartInput.zod as unknown as z.ZodObject<any>,
MessageV2.FilePartInput.zod as unknown as z.ZodObject<any>,
MessageV2.AgentPartInput.zod as unknown as z.ZodObject<any>,
MessageV2.SubtaskPartInput.zod as unknown as z.ZodObject<any>,
]),
),
const ModelRef = Schema.Struct({
providerID: ProviderID,
modelID: ModelID,
})
export const PromptInput = Schema.Struct({
sessionID: SessionID,
messageID: Schema.optional(MessageID),
model: Schema.optional(ModelRef),
agent: Schema.optional(Schema.String),
noReply: Schema.optional(Schema.Boolean),
tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)).annotate({
description:
"@deprecated tools and permissions have been merged, you can set permissions on the session itself now",
}),
format: Schema.optional(MessageV2.Format),
system: Schema.optional(Schema.String),
variant: Schema.optional(Schema.String),
parts: Schema.Array(
Schema.Union([
MessageV2.TextPartInput,
MessageV2.FilePartInput,
MessageV2.AgentPartInput,
MessageV2.SubtaskPartInput,
]).annotate({ discriminator: "type" }),
),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
// `z.discriminatedUnion` erases the discriminated members' shapes back to
// `{}` because the derived `.zod` on each input is typed as an opaque
// `z.ZodType`. Restore the precise `parts` type from the exported Schema
// input types so callers see a proper tagged union.
// `{}` when walked from the generic `z.ZodType` input. Restore the precise
// `parts` type from the exported Schema input types so callers see a proper
// tagged union.
type PartInputUnion =
| MessageV2.TextPartInput
| MessageV2.FilePartInput
| MessageV2.AgentPartInput
| MessageV2.SubtaskPartInput
export type PromptInput = Omit<z.infer<typeof PromptInput>, "parts"> & {
export type PromptInput = Omit<Schema.Schema.Type<typeof PromptInput>, "parts"> & {
parts: PartInputUnion[]
}
export const LoopInput = z.object({
sessionID: SessionID.zod,
})
export class LoopInput extends Schema.Class<LoopInput>("SessionPrompt.LoopInput")({
sessionID: SessionID,
}) {
static readonly zod = zod(this)
}
export const ShellInput = z.object({
sessionID: SessionID.zod,
messageID: MessageID.zod.optional(),
agent: z.string(),
model: z
.object({
providerID: ProviderID.zod,
modelID: ModelID.zod,
})
.optional(),
command: z.string(),
})
export type ShellInput = z.infer<typeof ShellInput>
export const ShellInput = Schema.Struct({
sessionID: SessionID,
messageID: Schema.optional(MessageID),
agent: Schema.String,
model: Schema.optional(ModelRef),
command: Schema.String,
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export type ShellInput = Schema.Schema.Type<typeof ShellInput>
export const CommandInput = z.object({
messageID: MessageID.zod.optional(),
sessionID: SessionID.zod,
agent: z.string().optional(),
model: z.string().optional(),
arguments: z.string(),
command: z.string(),
variant: z.string().optional(),
// Inlined (no `.meta({ ref })`) to keep the original SDK output — the
export const CommandInput = Schema.Struct({
messageID: Schema.optional(MessageID),
sessionID: SessionID,
agent: Schema.optional(Schema.String),
model: Schema.optional(Schema.String),
arguments: Schema.String,
command: Schema.String,
variant: Schema.optional(Schema.String),
// Inlined (no identifier annotation) to keep the original SDK output — the
// PromptInput call site below references FilePartInput by ref via the
// Schema export in message-v2.ts.
parts: z
.array(
z.discriminatedUnion("type", [
z.object({
id: PartID.zod.optional(),
type: z.literal("file"),
mime: z.string(),
filename: z.string().optional(),
url: z.string(),
source: MessageV2.FilePartSource.zod.optional(),
parts: Schema.optional(
Schema.Array(
Schema.Union([
Schema.Struct({
id: Schema.optional(PartID),
type: Schema.Literal("file"),
mime: Schema.String,
filename: Schema.optional(Schema.String),
url: Schema.String,
source: Schema.optional(MessageV2.FilePartSource),
}),
]),
)
.optional(),
})
export type CommandInput = z.infer<typeof CommandInput>
]).annotate({ discriminator: "type" }),
),
),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export type CommandInput = Schema.Schema.Type<typeof CommandInput>
/** @internal Exported for testing */
export function createStructuredOutputTool(input: {
+4 -29
View File
@@ -1,7 +1,6 @@
import type { NamedError } from "@opencode-ai/shared/util/error"
import { Cause, Clock, Duration, Effect, Schedule } from "effect"
import { MessageV2 } from "./message-v2"
import { iife } from "@/util/iife"
export type Err = ReturnType<NamedError["toObject"]>
@@ -63,43 +62,19 @@ export function retryable(error: Err) {
return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message
}
// Check for rate limit patterns in plain text error messages
const msg = error.data?.message
if (typeof msg === "string") {
const lower = msg.toLowerCase()
if (
lower.includes("rate increased too quickly") ||
lower.includes("rate limit") ||
lower.includes("too many requests")
/rate.?limit|too.?many.?requests|overloaded|exhausted|unavailable|service.?unavailable|429|5\d\d|internal.?error|network.?error|connection.?(?:error|refused|lost|reset)|fetch.?failed|upstream|upstream.?connect|reset before headers|socket.?hang.?up|other.?side.?closed|ended without|timed? out|timeout|terminated|retry delay|provider.?returned.?error|server.?error/i.test(
lower,
)
) {
if (lower.includes("overloaded") || lower.includes("exhausted") || lower.includes("unavailable")) return "Provider is overloaded"
return msg
}
}
const json = iife(() => {
try {
if (typeof error.data?.message === "string") {
const parsed = JSON.parse(error.data.message)
return parsed
}
return JSON.parse(error.data.message)
} catch {
return undefined
}
})
if (!json || typeof json !== "object") return undefined
const code = typeof json.code === "string" ? json.code : ""
if (json.type === "error" && json.error?.type === "too_many_requests") {
return "Too Many Requests"
}
if (code.includes("exhausted") || code.includes("unavailable")) {
return "Provider is overloaded"
}
if (json.type === "error" && typeof json.error?.code === "string" && json.error.code.includes("rate_limit")) {
return "Rate Limited"
}
return undefined
}
+9 -8
View File
@@ -1,10 +1,11 @@
import z from "zod"
import { Effect, Layer, Context } from "effect"
import { Effect, Layer, Context, Schema } from "effect"
import { Bus } from "../bus"
import { Snapshot } from "../snapshot"
import { Storage } from "@/storage"
import { SyncEvent } from "../sync"
import { Log } from "../util"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import * as Session from "./session"
import { MessageV2 } from "./message-v2"
import { SessionID, MessageID, PartID } from "./schema"
@@ -13,12 +14,12 @@ import { SessionSummary } from "./summary"
const log = Log.create({ service: "session.revert" })
export const RevertInput = z.object({
sessionID: SessionID.zod,
messageID: MessageID.zod,
partID: PartID.zod.optional(),
})
export type RevertInput = z.infer<typeof RevertInput>
export const RevertInput = Schema.Struct({
sessionID: SessionID,
messageID: MessageID,
partID: Schema.optional(PartID),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export type RevertInput = Schema.Schema.Type<typeof RevertInput>
export interface Interface {
readonly revert: (input: RevertInput) => Effect.Effect<Session.Info>
+104 -89
View File
@@ -27,7 +27,9 @@ import { SessionID, MessageID, PartID } from "./schema"
import type { Provider } from "@/provider"
import { Permission } from "@/permission"
import { Global } from "@/global"
import { Effect, Layer, Option, Context } from "effect"
import { Effect, Layer, Option, Context, Schema, Types } from "effect"
import { zod, zodObject } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
const log = Log.create({ service: "session" })
@@ -114,91 +116,104 @@ function getForkedTitle(title: string): string {
return `${title} (fork #1)`
}
export const Info = z
.object({
id: SessionID.zod,
slug: z.string(),
projectID: ProjectID.zod,
workspaceID: WorkspaceID.zod.optional(),
directory: z.string(),
parentID: SessionID.zod.optional(),
summary: z
.object({
additions: z.number(),
deletions: z.number(),
files: z.number(),
diffs: Snapshot.FileDiff.zod.array().optional(),
})
.optional(),
share: z
.object({
url: z.string(),
})
.optional(),
title: z.string(),
version: z.string(),
time: z.object({
created: z.number(),
updated: z.number(),
compacting: z.number().optional(),
archived: z.number().optional(),
}),
permission: Permission.Ruleset.zod.optional(),
revert: z
.object({
messageID: MessageID.zod,
partID: PartID.zod.optional(),
snapshot: z.string().optional(),
diff: z.string().optional(),
})
.optional(),
})
.meta({
ref: "Session",
})
export type Info = z.output<typeof Info>
export const ProjectInfo = z
.object({
id: ProjectID.zod,
name: z.string().optional(),
worktree: z.string(),
})
.meta({
ref: "ProjectSummary",
})
export type ProjectInfo = z.output<typeof ProjectInfo>
export const GlobalInfo = Info.extend({
project: ProjectInfo.nullable(),
}).meta({
ref: "GlobalSession",
const Summary = Schema.Struct({
additions: Schema.Number,
deletions: Schema.Number,
files: Schema.Number,
diffs: Schema.optional(Schema.Array(Snapshot.FileDiff)),
})
export type GlobalInfo = z.output<typeof GlobalInfo>
export const CreateInput = z
.object({
parentID: SessionID.zod.optional(),
title: z.string().optional(),
permission: Info.shape.permission,
workspaceID: WorkspaceID.zod.optional(),
})
.optional()
export type CreateInput = z.output<typeof CreateInput>
export const ForkInput = z.object({ sessionID: SessionID.zod, messageID: MessageID.zod.optional() })
export const GetInput = SessionID.zod
export const ChildrenInput = SessionID.zod
export const RemoveInput = SessionID.zod
export const SetTitleInput = z.object({ sessionID: SessionID.zod, title: z.string() })
export const SetArchivedInput = z.object({ sessionID: SessionID.zod, time: z.number().optional() })
export const SetPermissionInput = z.object({ sessionID: SessionID.zod, permission: Permission.Ruleset.zod })
export const SetRevertInput = z.object({
sessionID: SessionID.zod,
revert: Info.shape.revert,
summary: Info.shape.summary,
const Share = Schema.Struct({
url: Schema.String,
})
export const MessagesInput = z.object({ sessionID: SessionID.zod, limit: z.number().optional() })
const Time = Schema.Struct({
created: Schema.Number,
updated: Schema.Number,
compacting: Schema.optional(Schema.Number),
archived: Schema.optional(Schema.Number),
})
const Revert = Schema.Struct({
messageID: MessageID,
partID: Schema.optional(PartID),
snapshot: Schema.optional(Schema.String),
diff: Schema.optional(Schema.String),
})
export const Info = Schema.Struct({
id: SessionID,
slug: Schema.String,
projectID: ProjectID,
workspaceID: Schema.optional(WorkspaceID),
directory: Schema.String,
parentID: Schema.optional(SessionID),
summary: Schema.optional(Summary),
share: Schema.optional(Share),
title: Schema.String,
version: Schema.String,
time: Time,
permission: Schema.optional(Permission.Ruleset),
revert: Schema.optional(Revert),
})
.annotate({ identifier: "Session" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type Info = Types.DeepMutable<Schema.Schema.Type<typeof Info>>
export const ProjectInfo = Schema.Struct({
id: ProjectID,
name: Schema.optional(Schema.String),
worktree: Schema.String,
})
.annotate({ identifier: "ProjectSummary" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type ProjectInfo = Types.DeepMutable<Schema.Schema.Type<typeof ProjectInfo>>
export const GlobalInfo = Schema.Struct({
...Info.fields,
project: Schema.NullOr(ProjectInfo),
})
.annotate({ identifier: "GlobalSession" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type GlobalInfo = Types.DeepMutable<Schema.Schema.Type<typeof GlobalInfo>>
export const CreateInput = Schema.optional(
Schema.Struct({
parentID: Schema.optional(SessionID),
title: Schema.optional(Schema.String),
permission: Schema.optional(Permission.Ruleset),
workspaceID: Schema.optional(WorkspaceID),
}),
).pipe(withStatics((s) => ({ zod: zod(s) })))
export type CreateInput = Types.DeepMutable<Schema.Schema.Type<typeof CreateInput>>
export const ForkInput = Schema.Struct({
sessionID: SessionID,
messageID: Schema.optional(MessageID),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const GetInput = SessionID
export const ChildrenInput = SessionID
export const RemoveInput = SessionID
export const SetTitleInput = Schema.Struct({ sessionID: SessionID, title: Schema.String }).pipe(
withStatics((s) => ({ zod: zod(s) })),
)
export const SetArchivedInput = Schema.Struct({
sessionID: SessionID,
time: Schema.optional(Schema.Number),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const SetPermissionInput = Schema.Struct({
sessionID: SessionID,
permission: Permission.Ruleset,
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const SetRevertInput = Schema.Struct({
sessionID: SessionID,
revert: Schema.optional(Revert),
summary: Schema.optional(Summary),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const MessagesInput = Schema.Struct({
sessionID: SessionID,
limit: Schema.optional(Schema.Number),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const Event = {
Created: SyncEvent.define({
@@ -207,7 +222,7 @@ export const Event = {
aggregate: "sessionID",
schema: z.object({
sessionID: SessionID.zod,
info: Info,
info: Info.zod,
}),
}),
Updated: SyncEvent.define({
@@ -216,14 +231,14 @@ export const Event = {
aggregate: "sessionID",
schema: z.object({
sessionID: SessionID.zod,
info: updateSchema(Info).extend({
share: updateSchema(Info.shape.share.unwrap()).optional(),
time: updateSchema(Info.shape.time).optional(),
info: updateSchema(zodObject(Info)).extend({
share: updateSchema(zodObject(Share)).optional(),
time: updateSchema(zodObject(Time)).optional(),
}),
}),
busSchema: z.object({
sessionID: SessionID.zod,
info: Info,
info: Info.zod,
}),
}),
Deleted: SyncEvent.define({
@@ -232,7 +247,7 @@ export const Event = {
aggregate: "sessionID",
schema: z.object({
sessionID: SessionID.zod,
info: Info,
info: Info.zod,
}),
}),
Diff: BusEvent.define(
+21 -21
View File
@@ -2,35 +2,35 @@ import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { InstanceState } from "@/effect"
import { SessionID } from "./schema"
import { Effect, Layer, Context } from "effect"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { Effect, Layer, Context, Schema } from "effect"
import z from "zod"
export const Info = z
.union([
z.object({
type: z.literal("idle"),
}),
z.object({
type: z.literal("retry"),
attempt: z.number(),
message: z.string(),
next: z.number(),
}),
z.object({
type: z.literal("busy"),
}),
])
.meta({
ref: "SessionStatus",
})
export type Info = z.infer<typeof Info>
export const Info = Schema.Union([
Schema.Struct({
type: Schema.Literal("idle"),
}),
Schema.Struct({
type: Schema.Literal("retry"),
attempt: Schema.Number,
message: Schema.String,
next: Schema.Number,
}),
Schema.Struct({
type: Schema.Literal("busy"),
}),
])
.annotate({ identifier: "SessionStatus" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type Info = Schema.Schema.Type<typeof Info>
export const Event = {
Status: BusEvent.define(
"session.status",
z.object({
sessionID: SessionID.zod,
status: Info,
status: Info.zod,
}),
),
// deprecated
+8 -6
View File
@@ -1,8 +1,9 @@
import z from "zod"
import { Effect, Layer, Context } from "effect"
import { Effect, Layer, Context, Schema } from "effect"
import { Bus } from "@/bus"
import { Snapshot } from "@/snapshot"
import { Storage } from "@/storage"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import * as Session from "./session"
import { MessageV2 } from "./message-v2"
import { SessionID, MessageID } from "./schema"
@@ -155,9 +156,10 @@ export const defaultLayer = Layer.suspend(() =>
),
)
export const DiffInput = z.object({
sessionID: SessionID.zod,
messageID: MessageID.zod.optional(),
})
export const DiffInput = Schema.Struct({
sessionID: SessionID,
messageID: Schema.optional(MessageID),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export type DiffInput = Schema.Schema.Type<typeof DiffInput>
export * as SessionSummary from "./summary"
+14 -10
View File
@@ -1,26 +1,30 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { SessionID } from "./schema"
import { Effect, Layer, Context } from "effect"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { Effect, Layer, Context, Schema } from "effect"
import z from "zod"
import { Database, eq, asc } from "../storage"
import { TodoTable } from "./session.sql"
export const Info = z
.object({
content: z.string().describe("Brief description of the task"),
status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
priority: z.string().describe("Priority level of the task: high, medium, low"),
})
.meta({ ref: "Todo" })
export type Info = z.infer<typeof Info>
export const Info = Schema.Struct({
content: Schema.String.annotate({ description: "Brief description of the task" }),
status: Schema.String.annotate({
description: "Current status of the task: pending, in_progress, completed, cancelled",
}),
priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }),
})
.annotate({ identifier: "Todo" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type Info = Schema.Schema.Type<typeof Info>
export const Event = {
Updated: BusEvent.define(
"todo.updated",
z.object({
sessionID: SessionID.zod,
todos: z.array(Info),
todos: z.array(Info.zod),
}),
),
}
@@ -168,6 +168,7 @@ export async function run(db: SQLiteBunDatabase<any, any> | NodeSQLiteDatabase<a
vcs: data.vcs,
name: data.name ?? undefined,
icon_url: data.icon?.url,
icon_url_override: data.icon?.override,
icon_color: data.icon?.color,
time_created: data.time?.created ?? now,
time_updated: data.time?.updated ?? now,
+14 -1
View File
@@ -4,8 +4,21 @@ import * as Tool from "./tool"
import DESCRIPTION_WRITE from "./todowrite.txt"
import { Todo } from "../session/todo"
// Parameters are kept inline rather than derived from Todo.Info because
// Tool.define requires z.ZodObject-typed parameters for execute() inference,
// and zodObject(Todo.Info) returns ZodObject<any> — reaching into .shape would
// erase field types. Tool schemas migrate to Effect Schema as a separate slice
// per specs/effect/schema.md.
const parameters = z.object({
todos: z.array(z.object(Todo.Info.shape)).describe("The updated todo list"),
todos: z
.array(
z.object({
content: z.string().describe("Brief description of the task"),
status: z.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
priority: z.string().describe("Priority level of the task: high, medium, low"),
}),
)
.describe("The updated todo list"),
})
type Metadata = {
+27
View File
@@ -22,6 +22,33 @@ export function zod<S extends Schema.Top>(schema: S): z.ZodType<Schema.Schema.Ty
return walk(schema.ast) as z.ZodType<Schema.Schema.Type<S>>
}
/**
* Derive a Zod value from an Effect Schema (or a Schema-backed export with a
* `.zod` static) and narrow the result to `z.ZodObject<any>` so `.shape`,
* `.omit`, `.extend`, and friends are accessible.
*
* The `zod()` walker returns `z.ZodType<T>` because not every AST node decodes
* to an object; this helper keeps the "I started from a `Schema.Struct`" cast
* in one place instead of sprinkling `as unknown as z.ZodObject<any>` across
* call sites.
*
* The return is intentionally loose carrying Schema field types through the
* mapped `.omit()` / `.extend()` surface triggers brand-intersection
* explosions for branded primitives (`string & Brand<"SessionID">` extends
* `object` via the brand and gets walked into the prototype by `DeepPartial`,
* `updateSchema`, etc.), and zod's inference through `z.ZodType<T | undefined>`
* wrappers also can't reconstruct `T` cleanly. Consumers that care about the
* post-`.omit()` shape should cast `c.req.valid(...)` to the expected type.
*/
export function zodObject<S extends Schema.Top>(schema: S): z.ZodObject<any> {
const derived: z.ZodTypeAny = "zod" in schema && isZodType(schema.zod) ? schema.zod : walk(schema.ast)
return derived as unknown as z.ZodObject<any>
}
function isZodType(value: unknown): value is z.ZodTypeAny {
return typeof value === "object" && value !== null && "_zod" in value
}
function walk(ast: SchemaAST.AST): z.ZodTypeAny {
const cached = walkCache.get(ast)
if (cached) return cached
@@ -3,6 +3,7 @@ import { Effect, Layer, Stream } from "effect"
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { Installation } from "../../src/installation"
import { InstallationChannel } from "../../src/installation/version"
const encoder = new TextEncoder()
@@ -68,11 +69,15 @@ describe("installation", () => {
expect(result).toBe("4.0.0-beta.1")
})
test("reads npm registry versions", async () => {
test("reads npm versions via npm view", async () => {
const calls: string[][] = []
const layer = testLayer(
() => jsonResponse({ version: "1.5.0" }),
() => {
throw new Error("unexpected http request")
},
(cmd, args) => {
if (cmd === "npm" && args.includes("registry")) return "https://registry.npmjs.org\n"
calls.push([cmd, ...args])
if (cmd === "npm" && args[0] === "view") return '"1.5.0"\n'
return ""
},
)
@@ -81,18 +86,47 @@ describe("installation", () => {
Installation.Service.use((svc) => svc.latest("npm")).pipe(Effect.provide(layer)),
)
expect(result).toBe("1.5.0")
expect(calls).toContainEqual(["npm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"])
})
test("reads npm registry versions for bun method", async () => {
test("reads npm versions via bun pm view", async () => {
const calls: string[][] = []
const layer = testLayer(
() => jsonResponse({ version: "1.6.0" }),
() => "",
() => {
throw new Error("unexpected http request")
},
(cmd, args) => {
calls.push([cmd, ...args])
if (cmd === "bun" && args[0] === "pm") return '"1.6.0"\n'
return ""
},
)
const result = await Effect.runPromise(
Installation.Service.use((svc) => svc.latest("bun")).pipe(Effect.provide(layer)),
)
expect(result).toBe("1.6.0")
expect(calls).toContainEqual(["bun", "pm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"])
})
test("reads npm versions via pnpm view", async () => {
const calls: string[][] = []
const layer = testLayer(
() => {
throw new Error("unexpected http request")
},
(cmd, args) => {
calls.push([cmd, ...args])
if (cmd === "pnpm" && args[0] === "view") return '"1.7.0"\n'
return ""
},
)
const result = await Effect.runPromise(
Installation.Service.use((svc) => svc.latest("pnpm")).pipe(Effect.provide(layer)),
)
expect(result).toBe("1.7.0")
expect(calls).toContainEqual(["pnpm", "view", `opencode-ai@${InstallationChannel}`, "version", "--json"])
})
test("reads scoop manifest versions", async () => {
+125
View File
@@ -1,7 +1,58 @@
import fs from "fs/promises"
import path from "path"
import { describe, expect, test } from "bun:test"
import { Effect, Layer, Stream } from "effect"
import { NodeFileSystem } from "@effect/platform-node"
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Global } from "@opencode-ai/shared/global"
import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { Npm } from "../src/npm"
import { tmpdir } from "./fixture/fixture"
const win = process.platform === "win32"
const encoder = new TextEncoder()
function mockSpawner(handler: (cmd: string, args: readonly string[]) => string = () => "") {
const spawner = ChildProcessSpawner.make((command) => {
const std = ChildProcess.isStandardCommand(command) ? command : undefined
const output = handler(std?.command ?? "", std?.args ?? [])
return Effect.succeed(
ChildProcessSpawner.makeHandle({
pid: ChildProcessSpawner.ProcessId(0),
exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),
isRunning: Effect.succeed(false),
kill: () => Effect.void,
stdin: { [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") } as any,
stdout: output ? Stream.make(encoder.encode(output)) : Stream.empty,
stderr: Stream.empty,
all: Stream.empty,
getInputFd: () => ({ [Symbol.for("effect/Sink/TypeId")]: Symbol.for("effect/Sink/TypeId") }) as any,
getOutputFd: () => Stream.empty,
unref: Effect.succeed(Effect.void),
}),
)
})
return Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner)
}
function testLayer(spawnHandler?: (cmd: string, args: readonly string[]) => string) {
return Npm.layer.pipe(
Layer.provide(mockSpawner(spawnHandler)),
Layer.provide(EffectFlock.layer),
Layer.provide(AppFileSystem.layer),
Layer.provide(Global.layer),
Layer.provide(NodeFileSystem.layer),
)
}
const writePackage = (dir: string, pkg: Record<string, unknown>) =>
Bun.write(
path.join(dir, "package.json"),
JSON.stringify({
version: "1.0.0",
...pkg,
}),
)
describe("Npm.sanitize", () => {
test("keeps normal scoped package specs unchanged", () => {
@@ -16,3 +67,77 @@ describe("Npm.sanitize", () => {
expect(Npm.sanitize(spec)).toBe(expected)
})
})
describe("Npm.install", () => {
test("respects omit from project .npmrc", async () => {
await using tmp = await tmpdir()
await writePackage(tmp.path, {
name: "fixture",
dependencies: {
"prod-pkg": "file:./prod-pkg",
},
devDependencies: {
"dev-pkg": "file:./dev-pkg",
},
})
await Bun.write(path.join(tmp.path, ".npmrc"), "omit=dev\n")
await fs.mkdir(path.join(tmp.path, "prod-pkg"))
await fs.mkdir(path.join(tmp.path, "dev-pkg"))
await writePackage(path.join(tmp.path, "prod-pkg"), { name: "prod-pkg" })
await writePackage(path.join(tmp.path, "dev-pkg"), { name: "dev-pkg" })
await Npm.install(tmp.path)
await expect(fs.stat(path.join(tmp.path, "node_modules", "prod-pkg"))).resolves.toBeDefined()
await expect(fs.stat(path.join(tmp.path, "node_modules", "dev-pkg"))).rejects.toThrow()
})
})
describe("Npm.outdated", () => {
test("checks latest via npm view", async () => {
const calls: string[][] = []
const layer = testLayer((cmd, args) => {
calls.push([cmd, ...args])
if (cmd === "npm" && args[0] === "view") return '"2.0.0"\n'
return ""
})
const result = await Effect.runPromise(
Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer)),
)
expect(result).toBe(true)
expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"])
})
test("keeps range comparison behavior", async () => {
const layer = testLayer((cmd, args) => {
if (cmd === "npm" && args[0] === "view") return '"2.3.0"\n'
return ""
})
const result = await Effect.runPromise(
Npm.Service.use((svc) => svc.outdated("example", "^2.0.0")).pipe(Effect.provide(layer)),
)
expect(result).toBe(false)
})
test("falls back when npm view is unavailable", async () => {
const calls: string[][] = []
const layer = testLayer((cmd, args) => {
calls.push([cmd, ...args])
if (cmd === "pnpm" && args[0] === "view") return '"2.0.0"\n'
return ""
})
const result = await Effect.runPromise(
Npm.Service.use((svc) => svc.outdated("example", "1.0.0")).pipe(Effect.provide(layer)),
)
expect(result).toBe(true)
expect(calls).toContainEqual(["npm", "view", "example", "dist-tags.latest", "--json"])
expect(calls).toContainEqual(["pnpm", "view", "example", "dist-tags.latest", "--json"])
})
})
+44 -1
View File
@@ -278,6 +278,31 @@ describe("Project.discover", () => {
expect(updated).toBeDefined()
expect(updated!.icon).toBeUndefined()
})
test("should not discover favicon when override is set", async () => {
await using tmp = await tmpdir({ git: true })
const { project } = await run((svc) => svc.fromDirectory(tmp.path))
await run((svc) =>
svc.update({
projectID: project.id,
icon: { override: "data:image/png;base64,override" },
}),
)
const updatedProject = await run((svc) => svc.get(project.id))
if (!updatedProject) throw new Error("Project not found")
const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
await Bun.write(path.join(tmp.path, "favicon.png"), pngData)
await run((svc) => svc.discover(updatedProject))
const updated = Project.get(project.id)
expect(updated).toBeDefined()
expect(updated!.icon?.override).toBe("data:image/png;base64,override")
expect(updated!.icon?.url).toBeUndefined()
})
})
describe("Project.update", () => {
@@ -332,6 +357,23 @@ describe("Project.update", () => {
expect(fromDb?.icon?.color).toBe("#ff0000")
})
test("should update icon override", async () => {
await using tmp = await tmpdir({ git: true })
const { project } = await run((svc) => svc.fromDirectory(tmp.path))
const updated = await run((svc) =>
svc.update({
projectID: project.id,
icon: { override: "data:image/png;base64,abc123" },
}),
)
expect(updated.icon?.override).toBe("data:image/png;base64,abc123")
const fromDb = Project.get(project.id)
expect(fromDb?.icon?.override).toBe("data:image/png;base64,abc123")
})
test("should update commands", async () => {
await using tmp = await tmpdir({ git: true })
const { project } = await run((svc) => svc.fromDirectory(tmp.path))
@@ -389,13 +431,14 @@ describe("Project.update", () => {
svc.update({
projectID: project.id,
name: "Multi Update",
icon: { url: "https://example.com/favicon.ico", color: "#00ff00" },
icon: { url: "https://example.com/favicon.ico", override: "data:image/png;base64,abc123", color: "#00ff00" },
commands: { start: "make start" },
}),
)
expect(updated.name).toBe("Multi Update")
expect(updated.icon?.url).toBe("https://example.com/favicon.ico")
expect(updated.icon?.override).toBe("data:image/png;base64,abc123")
expect(updated.icon?.color).toBe("#00ff00")
expect(updated.commands?.start).toBe("make start")
})
@@ -18,7 +18,7 @@ const svc = {
create(input?: SessionNs.CreateInput) {
return run(SessionNs.Service.use((svc) => svc.create(input)))
},
setArchived(input: z.output<typeof SessionNs.SetArchivedInput>) {
setArchived(input: z.output<typeof SessionNs.SetArchivedInput.zod>) {
return run(SessionNs.Service.use((svc) => svc.setArchived(input)))
},
}
@@ -38,7 +38,7 @@ const svc = {
create(input?: SessionNs.CreateInput) {
return run(SessionNs.Service.use((svc) => svc.create(input)))
},
messages(input: z.output<typeof SessionNs.MessagesInput>) {
messages(input: z.output<typeof SessionNs.MessagesInput.zod>) {
return run(SessionNs.Service.use((svc) => svc.messages(input)))
},
updateMessage<T extends MessageV2.Info>(msg: T) {
+94 -5
View File
@@ -124,22 +124,22 @@ describe("session.retry.delay", () => {
})
describe("session.retry.retryable", () => {
test("maps too_many_requests json messages", () => {
test("retries too_many_requests in raw json strings", () => {
const error = wrap(JSON.stringify({ type: "error", error: { type: "too_many_requests" } }))
expect(SessionRetry.retryable(error)).toBe("Too Many Requests")
expect(SessionRetry.retryable(error)).toBe(JSON.stringify({ type: "error", error: { type: "too_many_requests" } }))
})
test("maps overloaded provider codes", () => {
test("retries exhausted codes in raw json strings", () => {
const error = wrap(JSON.stringify({ code: "resource_exhausted" }))
expect(SessionRetry.retryable(error)).toBe("Provider is overloaded")
})
test("does not retry unknown json messages", () => {
test("does not retry unknown raw json strings", () => {
const error = wrap(JSON.stringify({ error: { message: "no_kv_space" } }))
expect(SessionRetry.retryable(error)).toBeUndefined()
})
test("does not throw on numeric error codes", () => {
test("does not throw on numeric codes in raw json strings", () => {
const error = wrap(JSON.stringify({ type: "error", error: { code: 123 } }))
const result = SessionRetry.retryable(error)
expect(result).toBeUndefined()
@@ -169,6 +169,66 @@ describe("session.retry.retryable", () => {
expect(SessionRetry.retryable(error)).toBe(msg)
})
test("retries connection errors in plain text", () => {
const msg = "Connection refused"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
})
test("retries timeout errors in plain text", () => {
const msg = "Request timed out"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
})
test("retries 500 errors in plain text", () => {
const msg = "HTTP 500 Internal Server Error"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
})
test("retries overloaded errors in plain text", () => {
const msg = "Provider is overloaded"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe("Provider is overloaded")
})
test("retries 429 errors in plain text", () => {
const msg = "HTTP 429 Too Many Requests"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
})
test("retries provider returned errors", () => {
const msg = "Provider returned error: something went wrong"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
})
test("retries other side closed errors", () => {
const msg = "Other side closed connection"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
})
test("retries reset before headers errors", () => {
const msg = "Connection reset before headers"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
})
test("retries ended without errors", () => {
const msg = "Request ended without sending chunks"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
})
test("retries retry delay exceeded errors", () => {
const msg = "Retry delay exceeded"
const error = wrap(msg)
expect(SessionRetry.retryable(error)).toBe(msg)
})
test("does not retry context overflow errors", () => {
const error = new MessageV2.ContextOverflowError({
message: "Input exceeds context window of this model",
@@ -230,6 +290,35 @@ describe("session.retry.retryable", () => {
expect(retryable).toBeDefined()
expect(retryable).toBe("Response decompression failed")
})
test("returns upsell message for FreeUsageLimitError in response body", () => {
const error = new MessageV2.APIError({
message: "Usage limit exceeded",
isRetryable: true,
responseBody: '{"error":{"type":"FreeUsageLimitError"}}',
}).toObject() as MessageV2.APIError
expect(SessionRetry.retryable(error)).toBe(SessionRetry.GO_UPSELL_MESSAGE)
})
test("returns overloaded message when APIError message includes Overloaded", () => {
const error = new MessageV2.APIError({
message: "Server is Overloaded, try again later",
isRetryable: true,
}).toObject() as MessageV2.APIError
expect(SessionRetry.retryable(error)).toBe("Provider is overloaded")
})
test("retries rate_limit codes in raw json strings", () => {
const error = wrap(JSON.stringify({ type: "error", error: { code: "rate_limit_exceeded" } }))
expect(SessionRetry.retryable(error)).toBe(JSON.stringify({ type: "error", error: { code: "rate_limit_exceeded" } }))
})
test("retries server_error in raw json strings", () => {
const error = wrap(JSON.stringify({ type: "error", error: { type: "server_error", code: "server_error" } }))
expect(SessionRetry.retryable(error)).toBe(JSON.stringify({ type: "error", error: { type: "server_error", code: "server_error" } }))
})
})
describe("session.message-v2.fromError", () => {
@@ -0,0 +1,310 @@
import { describe, expect, test } from "bun:test"
import { Schema } from "effect"
import { Session } from "../../src/session"
import { SessionPrompt } from "../../src/session/prompt"
import { SessionRevert } from "../../src/session/revert"
import { SessionStatus } from "../../src/session/status"
import { SessionSummary } from "../../src/session/summary"
import { Todo } from "../../src/session/todo"
import { SessionID, MessageID, PartID } from "../../src/session/schema"
import { ProjectID } from "../../src/project/schema"
import { WorkspaceID } from "../../src/control-plane/schema"
// Covers the session-domain Effect Schema migration. For each migrated
// schema we assert:
// 1. The Effect decoder (`Schema.decodeUnknownSync`) accepts valid input.
// 2. The derived Zod (`X.zod.parse`) accepts the same input and returns the
// same shape.
// 3. Clearly-invalid input is rejected by both paths.
//
// The point is to lock down the Schema <-> Zod bridge so a future edit to
// any input schema can't silently drop or widen a field on one side.
// Representative valid IDs — the branded schemas require the right prefix
// (see src/id/id.ts).
const sessionID = SessionID.zod.parse("ses_01J5Y5H0AH4Q4NXJ6P4C3P5V2K")
const sessionIDChild = SessionID.zod.parse("ses_01J5Y5H0AH4Q4NXJ6P4C3P5V2L")
const messageID = MessageID.zod.parse("msg_01J5Y5H0AH4Q4NXJ6P4C3P5V2M")
const partID = PartID.zod.parse("prt_01J5Y5H0AH4Q4NXJ6P4C3P5V2N")
const projectID = ProjectID.zod.parse("proj-alpha")
const workspaceID = WorkspaceID.zod.parse("wrk-primary")
function decodeUnknown<S extends Schema.Top>(schema: S) {
const decode = Schema.decodeUnknownSync(schema as any)
return (input: unknown): Schema.Schema.Type<S> => decode(input) as Schema.Schema.Type<S>
}
describe("Session.Info", () => {
const decode = decodeUnknown(Session.Info)
test("accepts minimal session", () => {
const input = {
id: sessionID,
slug: "hello",
projectID,
directory: "/tmp/proj",
title: "First session",
version: "0.1.0",
time: { created: 1, updated: 2 },
}
expect(decode(input)).toEqual(input)
expect(Session.Info.zod.parse(input)).toEqual(input)
})
test("round-trips every optional field", () => {
const input = {
id: sessionID,
slug: "fullshape",
projectID,
workspaceID,
directory: "/tmp/proj",
parentID: sessionIDChild,
summary: {
additions: 10,
deletions: 5,
files: 2,
diffs: [{ additions: 1, deletions: 0, file: "a.ts", patch: "--- a/a.ts" }],
},
share: { url: "https://share.example.com/s/1" },
title: "Full session",
version: "1.0.0",
time: { created: 100, updated: 200, compacting: 150, archived: 300 },
permission: [{ action: "allow" as const, pattern: "*", permission: "read" }],
revert: {
messageID,
partID,
snapshot: "snap-1",
diff: "diff-1",
},
}
expect(decode(input)).toEqual(input)
expect(Session.Info.zod.parse(input)).toEqual(input)
})
test("rejects unbranded session id", () => {
const bad = { id: "not-a-session-id" } as unknown
expect(() => decode(bad)).toThrow()
expect(() => Session.Info.zod.parse(bad)).toThrow()
})
test("rejects missing required fields", () => {
const bad = { id: sessionID } as unknown
expect(() => decode(bad)).toThrow()
expect(() => Session.Info.zod.parse(bad)).toThrow()
})
})
describe("Session.ProjectInfo", () => {
const decode = decodeUnknown(Session.ProjectInfo)
test("accepts with and without optional name", () => {
const noName = { id: projectID, worktree: "/tmp/wt" }
const withName = { ...noName, name: "alpha" }
expect(decode(noName)).toEqual(noName)
expect(decode(withName)).toEqual(withName)
expect(Session.ProjectInfo.zod.parse(noName)).toEqual(noName)
expect(Session.ProjectInfo.zod.parse(withName)).toEqual(withName)
})
})
describe("Session.GlobalInfo", () => {
const decode = decodeUnknown(Session.GlobalInfo)
test("accepts null project", () => {
const input = {
id: sessionID,
slug: "global",
projectID,
directory: "/tmp/proj",
title: "global",
version: "0",
time: { created: 0, updated: 0 },
project: null,
}
expect(decode(input)).toEqual(input)
expect(Session.GlobalInfo.zod.parse(input)).toEqual(input)
})
test("accepts populated project", () => {
const input = {
id: sessionID,
slug: "global",
projectID,
directory: "/tmp/proj",
title: "global",
version: "0",
time: { created: 0, updated: 0 },
project: { id: projectID, worktree: "/tmp/wt", name: "alpha" },
}
expect(decode(input)).toEqual(input)
expect(Session.GlobalInfo.zod.parse(input)).toEqual(input)
})
})
describe("Session input schemas", () => {
test("CreateInput accepts undefined and populated forms", () => {
const decode = decodeUnknown(Session.CreateInput)
expect(decode(undefined)).toBeUndefined()
expect(Session.CreateInput.zod.parse(undefined)).toBeUndefined()
const populated = {
parentID: sessionID,
title: "child",
permission: [{ action: "ask" as const, pattern: "*", permission: "bash" }],
workspaceID,
}
expect(decode(populated)).toEqual(populated)
expect(Session.CreateInput.zod.parse(populated)).toEqual(populated)
})
test("ForkInput round-trips", () => {
const decode = decodeUnknown(Session.ForkInput)
const input = { sessionID, messageID }
expect(decode(input)).toEqual(input)
expect(Session.ForkInput.zod.parse(input)).toEqual(input)
// messageID is optional
const bare = { sessionID }
expect(decode(bare)).toEqual(bare)
expect(Session.ForkInput.zod.parse(bare)).toEqual(bare)
})
test("SetTitleInput rejects missing title", () => {
expect(() => decodeUnknown(Session.SetTitleInput)({ sessionID })).toThrow()
expect(() => Session.SetTitleInput.zod.parse({ sessionID })).toThrow()
})
test("SetArchivedInput accepts both with and without time", () => {
const decode = decodeUnknown(Session.SetArchivedInput)
expect(decode({ sessionID })).toEqual({ sessionID })
expect(decode({ sessionID, time: 123 })).toEqual({ sessionID, time: 123 })
})
test("SetPermissionInput requires a ruleset", () => {
const decode = decodeUnknown(Session.SetPermissionInput)
const input = { sessionID, permission: [{ action: "deny" as const, pattern: "*", permission: "write" }] }
expect(decode(input)).toEqual(input)
expect(() => decode({ sessionID })).toThrow()
})
test("MessagesInput accepts optional limit", () => {
const decode = decodeUnknown(Session.MessagesInput)
expect(decode({ sessionID })).toEqual({ sessionID })
expect(decode({ sessionID, limit: 50 })).toEqual({ sessionID, limit: 50 })
})
})
describe("SessionRevert.RevertInput", () => {
const decode = decodeUnknown(SessionRevert.RevertInput)
test("messageID is required, partID is optional", () => {
const withPart = { sessionID, messageID, partID }
expect(decode(withPart)).toEqual(withPart)
expect(SessionRevert.RevertInput.zod.parse(withPart)).toEqual(withPart)
const noPart = { sessionID, messageID }
expect(decode(noPart)).toEqual(noPart)
expect(SessionRevert.RevertInput.zod.parse(noPart)).toEqual(noPart)
expect(() => decode({ sessionID })).toThrow()
expect(() => SessionRevert.RevertInput.zod.parse({ sessionID })).toThrow()
})
})
describe("SessionSummary.DiffInput", () => {
const decode = decodeUnknown(SessionSummary.DiffInput)
test("messageID optional", () => {
expect(decode({ sessionID })).toEqual({ sessionID })
expect(decode({ sessionID, messageID })).toEqual({ sessionID, messageID })
})
})
describe("SessionStatus.Info", () => {
const decode = decodeUnknown(SessionStatus.Info)
test("idle / busy discriminators", () => {
expect(decode({ type: "idle" })).toEqual({ type: "idle" })
expect(decode({ type: "busy" })).toEqual({ type: "busy" })
expect(SessionStatus.Info.zod.parse({ type: "idle" })).toEqual({ type: "idle" })
})
test("retry carries attempt/message/next", () => {
const input = { type: "retry" as const, attempt: 1, message: "transient", next: 500 }
expect(decode(input)).toEqual(input)
expect(SessionStatus.Info.zod.parse(input)).toEqual(input)
})
test("rejects unknown type", () => {
expect(() => decode({ type: "bogus" })).toThrow()
expect(() => SessionStatus.Info.zod.parse({ type: "bogus" })).toThrow()
})
})
describe("Todo.Info", () => {
const decode = decodeUnknown(Todo.Info)
test("three-field round-trip", () => {
const input = { content: "do a thing", status: "pending", priority: "high" }
expect(decode(input)).toEqual(input)
expect(Todo.Info.zod.parse(input)).toEqual(input)
})
})
describe("SessionPrompt input schemas", () => {
test("LoopInput is just sessionID", () => {
const decode = decodeUnknown(SessionPrompt.LoopInput)
expect(decode({ sessionID })).toEqual({ sessionID })
expect(SessionPrompt.LoopInput.zod.parse({ sessionID } as unknown)).toEqual({ sessionID })
})
test("ShellInput requires agent + command", () => {
const decode = decodeUnknown(SessionPrompt.ShellInput)
const expected = { sessionID, agent: "build", command: "echo hi" }
const input: unknown = expected
expect(decode(input)).toEqual(expected)
expect(SessionPrompt.ShellInput.zod.parse(input as unknown)).toEqual(expected)
expect(() => decode({ sessionID })).toThrow()
})
test("PromptInput accepts a text part and a file part", () => {
const decode = decodeUnknown(SessionPrompt.PromptInput)
const expected = {
sessionID,
parts: [
{ type: "text" as const, text: "hello" },
{ type: "file" as const, mime: "image/png", url: "data:image/png;base64,AAAA" },
],
}
const input: unknown = expected
const decoded = decode(input)
expect(decoded.parts).toHaveLength(2)
expect(decoded.parts[0]).toMatchObject({ type: "text", text: "hello" })
expect(decoded.parts[1]).toMatchObject({ type: "file", mime: "image/png" })
const viaZod = SessionPrompt.PromptInput.zod.parse(input)
expect(viaZod.parts).toHaveLength(2)
})
test("PromptInput rejects unknown part type", () => {
const decode = decodeUnknown(SessionPrompt.PromptInput)
const bad = {
sessionID,
parts: [{ type: "nonsense", payload: 42 }],
}
expect(() => decode(bad)).toThrow()
expect(() => SessionPrompt.PromptInput.zod.parse(bad)).toThrow()
})
test("CommandInput round-trips core fields", () => {
const decode = decodeUnknown(SessionPrompt.CommandInput)
const expected = {
sessionID,
arguments: "--flag",
command: "deploy",
}
const input: unknown = expected
expect(decode(input)).toEqual(expected)
expect(SessionPrompt.CommandInput.zod.parse(input)).toEqual(expected)
})
})
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.14.21",
"version": "1.14.22",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.14.21",
"version": "1.14.22",
"type": "module",
"license": "MIT",
"scripts": {
+23 -27
View File
@@ -4174,34 +4174,30 @@
"parts": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^prt.*"
},
"type": {
"type": "string",
"const": "file"
},
"mime": {
"type": "string"
},
"filename": {
"type": "string"
},
"url": {
"type": "string"
},
"source": {
"$ref": "#/components/schemas/FilePartSource"
}
},
"required": ["type", "mime", "url"]
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^prt.*"
},
"type": {
"type": "string",
"const": "file"
},
"mime": {
"type": "string"
},
"filename": {
"type": "string"
},
"url": {
"type": "string"
},
"source": {
"$ref": "#/components/schemas/FilePartSource"
}
]
},
"required": ["type", "mime", "url"]
}
}
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.14.21",
"version": "1.14.22",
"name": "@opencode-ai/shared",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.14.21",
"version": "1.14.22",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.14.21",
"version": "1.14.22",
"type": "module",
"license": "MIT",
"exports": {
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@opencode-ai/web",
"type": "module",
"license": "MIT",
"version": "1.14.21",
"version": "1.14.22",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
+6 -2
View File
@@ -85,8 +85,8 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -96,6 +96,7 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
يستخدم [معرّف النموذج](/docs/config/#models) في إعدادات OpenCode الصيغة `opencode/<model-id>`. على سبيل المثال، بالنسبة إلى GPT 5.4، ستستخدم `opencode/gpt-5.4` في إعداداتك.
@@ -121,6 +122,7 @@ https://opencode.ai/zen/v1/models
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -170,6 +172,7 @@ https://opencode.ai/zen/v1/models
- MiniMax M2.5 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج.
- Ling 2.6 Flash Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج.
- Hy3 Preview Flash Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج.
- Nemotron 3 Super Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج.
- Big Pickle نموذج خفي ومتاح مجانا على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج.
@@ -214,6 +217,7 @@ https://opencode.ai/zen/v1/models
- Big Pickle: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج.
- MiniMax M2.5 Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج.
- Ling 2.6 Flash Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج.
- Hy3 Preview Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج.
- Nemotron 3 Super Free (نقاط نهاية NVIDIA المجانية): يُقدَّم بموجب [شروط خدمة النسخة التجريبية من واجهة NVIDIA API](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). للاستخدام التجريبي فقط، وليس للإنتاج أو البيانات الحساسة. تقوم NVIDIA بتسجيل المطالبات والمخرجات لتحسين نماذجها وخدماتها. لا ترسل بيانات شخصية أو سرية.
- OpenAI APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -90,8 +90,8 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa.
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -101,6 +101,7 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa.
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
[model id](/docs/config/#models) u vašoj OpenCode konfiguraciji koristi format
@@ -128,6 +129,7 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**.
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -177,6 +179,7 @@ Besplatni modeli:
- MiniMax M2.5 Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model.
- Ling 2.6 Flash Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model.
- Hy3 Preview Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model.
- Nemotron 3 Super Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model.
- Big Pickle je stealth model koji je besplatan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model.
@@ -226,6 +229,7 @@ i ne koriste vaše podatke za treniranje modela, uz sljedeće izuzetke:
- Big Pickle: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela.
- MiniMax M2.5 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela.
- Ling 2.6 Flash Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela.
- Hy3 Preview Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela.
- Nemotron 3 Super Free (besplatni NVIDIA endpointi): Dostupan je prema [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Samo za probnu upotrebu, nije za produkciju niti osjetljive podatke. NVIDIA bilježi promptove i izlaze radi poboljšanja svojih modela i usluga. Nemojte slati lične ili povjerljive podatke.
- OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -90,8 +90,8 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints.
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -101,6 +101,7 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints.
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
[model id](/docs/config/#models) i din OpenCode-konfiguration
@@ -128,6 +129,7 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**.
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -177,6 +179,7 @@ De gratis modeller:
- MiniMax M2.5 Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen.
- Ling 2.6 Flash Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen.
- Hy3 Preview Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen.
- Nemotron 3 Super Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen.
- Big Pickle er en stealth-model, som er gratis på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen.
@@ -224,6 +227,7 @@ Alle vores modeller hostes i US. Vores udbydere følger en nul-opbevaringspoliti
- Big Pickle: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen.
- MiniMax M2.5 Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen.
- Ling 2.6 Flash Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen.
- Hy3 Preview Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen.
- Nemotron 3 Super Free (gratis NVIDIA-endpoints): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun til prøvebrug, ikke til produktion eller følsomme data. Prompts og outputs logges af NVIDIA for at forbedre deres modeller og tjenester. Indsend ikke personlige eller fortrolige data.
- OpenAI APIs: Anmodninger opbevares i 30 dage i overensstemmelse med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: Anmodninger opbevares i 30 dage i overensstemmelse med [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -81,8 +81,8 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen.
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -92,6 +92,7 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen.
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
Die [Model-ID](/docs/config/#models) in deiner OpenCode-Konfiguration verwendet das Format `opencode/<model-id>`. Für GPT 5.4 würdest du zum Beispiel `opencode/gpt-5.4` in deiner Konfiguration verwenden.
@@ -117,6 +118,7 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -166,6 +168,7 @@ Die kostenlosen Modelle:
- MiniMax M2.5 Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern.
- Ling 2.6 Flash Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern.
- Hy3 Preview Flash Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern.
- Nemotron 3 Super Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern.
- Big Pickle ist ein Stealth-Modell, das für begrenzte Zeit kostenlos auf OpenCode verfügbar ist. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern.
@@ -210,6 +213,7 @@ Alle unsere Modelle werden in den USA gehostet. Unsere Provider folgen einer Zer
- Big Pickle: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden.
- MiniMax M2.5 Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden.
- Ling 2.6 Flash Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden.
- Hy3 Preview Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden.
- Nemotron 3 Super Free (kostenlose NVIDIA-Endpunkte): Bereitgestellt gemäß den [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Nur für Testzwecke, nicht für Produktion oder sensible Daten. Eingaben und Ausgaben werden von NVIDIA protokolliert, um seine Modelle und Dienste zu verbessern. Übermitteln Sie keine personenbezogenen oder vertraulichen Daten.
- OpenAI APIs: Anfragen werden in Übereinstimmung mit [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 30 Tage lang gespeichert.
- Anthropic APIs: Anfragen werden in Übereinstimmung mit [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 30 Tage lang gespeichert.
+6 -2
View File
@@ -90,8 +90,8 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -101,6 +101,7 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
El [identificador del modelo](/docs/config/#models) en tu configuración de OpenCode
@@ -128,6 +129,7 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -177,6 +179,7 @@ Los modelos gratuitos:
- MiniMax M2.5 Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo.
- Ling 2.6 Flash Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo.
- Hy3 Preview Flash Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo.
- Nemotron 3 Super Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo.
- Big Pickle es un modelo stealth que es gratuito en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo.
@@ -224,6 +227,7 @@ Todos nuestros modelos están alojados en US. Nuestros proveedores siguen una po
- Big Pickle: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo.
- MiniMax M2.5 Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo.
- Ling 2.6 Flash Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo.
- Hy3 Preview Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo.
- Nemotron 3 Super Free (endpoints gratuitos de NVIDIA): Se ofrece bajo los [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo para uso de prueba, no para producción ni datos sensibles. NVIDIA registra los prompts y las salidas para mejorar sus modelos y servicios. No envíes datos personales ni confidenciales.
- OpenAI APIs: Las solicitudes se conservan durante 30 días de acuerdo con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: Las solicitudes se conservan durante 30 días de acuerdo con [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -81,8 +81,8 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -92,6 +92,7 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
Le [model id](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode/<model-id>`. Par exemple, pour GPT 5.4, vous utiliseriez `opencode/gpt-5.4` dans votre configuration.
@@ -117,6 +118,7 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -166,6 +168,7 @@ Les modèles gratuits :
- MiniMax M2.5 Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle.
- Ling 2.6 Flash Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle.
- Hy3 Preview Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle.
- Nemotron 3 Super Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle.
- Big Pickle est un modèle stealth gratuit sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle.
@@ -210,6 +213,7 @@ Tous nos modèles sont hébergés aux US. Nos fournisseurs suivent une politique
- Big Pickle : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle.
- MiniMax M2.5 Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle.
- Ling 2.6 Flash Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle.
- Hy3 Preview Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle.
- Nemotron 3 Super Free (endpoints NVIDIA gratuits) : Fourni dans le cadre des [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Réservé à un usage d'essai, pas à la production ni aux données sensibles. Les prompts et les sorties sont journalisés par NVIDIA pour améliorer ses modèles et services. N'envoyez pas de données personnelles ou confidentielles.
- OpenAI APIs : Les requêtes sont conservées pendant 30 jours conformément à [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs : Les requêtes sont conservées pendant 30 jours conformément à [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -90,8 +90,8 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API.
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -101,6 +101,7 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API.
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
Il [model id](/docs/config/#models) nella config di OpenCode
@@ -128,6 +129,7 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**.
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -177,6 +179,7 @@ I modelli gratuiti:
- MiniMax M2.5 Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello.
- Ling 2.6 Flash Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello.
- Hy3 Preview Flash Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello.
- Nemotron 3 Super Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello.
- Big Pickle è un modello stealth che è gratuito su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello.
@@ -224,6 +227,7 @@ Tutti i nostri modelli sono ospitati negli US. I nostri provider seguono una pol
- Big Pickle: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello.
- MiniMax M2.5 Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello.
- Ling 2.6 Flash Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello.
- Hy3 Preview Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello.
- Nemotron 3 Super Free (endpoint NVIDIA gratuiti): fornito secondo i [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo per uso di prova, non per produzione o dati sensibili. NVIDIA registra prompt e output per migliorare i propri modelli e servizi. Non inviare dati personali o riservati.
- OpenAI APIs: le richieste vengono conservate per 30 giorni in conformità con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: le richieste vengono conservate per 30 giorni in conformità con [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -81,8 +81,8 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -92,6 +92,7 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
OpenCode 設定で使う [model id](/docs/config/#models) は `opencode/<model-id>` 形式です。たとえば、GPT 5.4 では設定に `opencode/gpt-5.4` を使用します。
@@ -117,6 +118,7 @@ https://opencode.ai/zen/v1/models
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -166,6 +168,7 @@ https://opencode.ai/zen/v1/models
- MiniMax M2.5 Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。
- Ling 2.6 Flash Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。
- Hy3 Preview Flash Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。
- Nemotron 3 Super Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。
- Big Pickle はステルスモデルで、期間限定で OpenCode で無料提供されています。チームはこの期間中にフィードバックを集め、モデルを改善しています。
@@ -210,6 +213,7 @@ https://opencode.ai/zen/v1/models
- Big Pickle: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。
- MiniMax M2.5 Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。
- Ling 2.6 Flash Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。
- Hy3 Preview Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。
- Nemotron 3 Super FreeNVIDIA の無料エンドポイント): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) に基づいて提供されます。試用専用であり、本番環境や機密性の高いデータには使用しないでください。プロンプトと出力は、NVIDIA が自社のモデルとサービスを改善するために記録します。個人情報や機密データは送信しないでください。
- OpenAI APIs: リクエストは [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) に従って 30 日間保持されます。
- Anthropic APIs: リクエストは [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) に従って 30 日間保持されます。
+6 -2
View File
@@ -81,8 +81,8 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다.
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -92,6 +92,7 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다.
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
OpenCode config에서 사용하는 [모델 ID](/docs/config/#models)는 `opencode/<model-id>` 형식입니다. 예를 들어 GPT 5.4를 사용하려면 config에서 `opencode/gpt-5.4`를 사용하면 됩니다.
@@ -117,6 +118,7 @@ https://opencode.ai/zen/v1/models
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -166,6 +168,7 @@ https://opencode.ai/zen/v1/models
- MiniMax M2.5 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다.
- Ling 2.6 Flash Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다.
- Hy3 Preview Flash Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다.
- Nemotron 3 Super Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다.
- Big Pickle은 한정된 기간 동안 OpenCode에서 무료로 제공되는 stealth model입니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다.
@@ -210,6 +213,7 @@ https://opencode.ai/zen/v1/models
- Big Pickle: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다.
- MiniMax M2.5 Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다.
- Ling 2.6 Flash Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다.
- Hy3 Preview Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다.
- Nemotron 3 Super Free(NVIDIA 무료 엔드포인트): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)에 따라 제공됩니다. 평가판 전용이며 프로덕션 환경이나 민감한 데이터에는 사용할 수 없습니다. NVIDIA는 자사 모델과 서비스를 개선하기 위해 프롬프트와 출력을 기록합니다. 개인 정보나 기밀 데이터는 제출하지 마세요.
- OpenAI APIs: 요청은 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data)에 따라 30일 동안 보관됩니다.
- Anthropic APIs: 요청은 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage)에 따라 30일 동안 보관됩니다.
+6 -2
View File
@@ -90,8 +90,8 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter.
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -101,6 +101,7 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter.
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
[modell-id](/docs/config/#models) i OpenCode-konfigurasjonen din
@@ -128,6 +129,7 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**.
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -177,6 +179,7 @@ Gratis-modellene:
- MiniMax M2.5 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen.
- Ling 2.6 Flash Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen.
- Hy3 Preview Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen.
- Nemotron 3 Super Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen.
- Big Pickle er en stealth-modell som er gratis på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen.
@@ -224,6 +227,7 @@ Alle modellene våre hostes i US. Leverandørene våre følger en policy for zer
- Big Pickle: I gratisperioden kan innsamlede data brukes til å forbedre modellen.
- MiniMax M2.5 Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen.
- Ling 2.6 Flash Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen.
- Hy3 Preview Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen.
- Nemotron 3 Super Free (gratis NVIDIA-endepunkter): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun for prøvebruk, ikke for produksjon eller sensitive data. Prompter og svar logges av NVIDIA for å forbedre modellene og tjenestene deres. Ikke send inn personopplysninger eller konfidensielle data.
- OpenAI APIs: Forespørsler lagres i 30 dager i samsvar med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: Forespørsler lagres i 30 dager i samsvar med [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -90,8 +90,8 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API.
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -101,6 +101,7 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API.
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
[ID modelu](/docs/config/#models) w Twojej konfiguracji OpenCode używa formatu
@@ -128,6 +129,7 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów*
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -178,6 +180,7 @@ Darmowe modele:
- MiniMax M2.5 Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu.
- Ling 2.6 Flash Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu.
- Hy3 Preview Flash Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu.
- Nemotron 3 Super Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu.
- Big Pickle to stealth model, który jest darmowy w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu.
@@ -225,6 +228,7 @@ Wszystkie nasze modele są hostowane w US. Nasi dostawcy stosują politykę zero
- Big Pickle: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu.
- MiniMax M2.5 Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu.
- Ling 2.6 Flash Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu.
- Hy3 Preview Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu.
- Nemotron 3 Super Free (darmowe endpointy NVIDIA): Udostępniany zgodnie z [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Tylko do użytku próbnego, nie do produkcji ani danych wrażliwych. NVIDIA rejestruje prompty i odpowiedzi, aby ulepszać swoje modele i usługi. Nie przesyłaj danych osobowych ani poufnych.
- OpenAI APIs: Żądania są przechowywane przez 30 dni zgodnie z [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: Żądania są przechowywane przez 30 dni zgodnie z [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -81,8 +81,8 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API.
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -92,6 +92,7 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API.
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
O [model id](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode/<model-id>`. Por exemplo, para GPT 5.4, você usaria `opencode/gpt-5.4` na sua configuração.
@@ -117,6 +118,7 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**.
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -166,6 +168,7 @@ Os modelos gratuitos:
- MiniMax M2.5 Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo.
- Ling 2.6 Flash Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo.
- Hy3 Preview Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo.
- Nemotron 3 Super Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo.
- Big Pickle é um modelo stealth que está gratuito no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo.
@@ -210,6 +213,7 @@ Todos os nossos modelos são hospedados nos US. Nossos provedores seguem uma pol
- Big Pickle: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo.
- MiniMax M2.5 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo.
- Ling 2.6 Flash Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo.
- Hy3 Preview Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo.
- Nemotron 3 Super Free (endpoints gratuitos da NVIDIA): Fornecido sob os [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Apenas para uso de avaliação, não para produção nem dados sensíveis. A NVIDIA registra prompts e saídas para melhorar seus modelos e serviços. Não envie dados pessoais ou confidenciais.
- OpenAI APIs: As solicitações são retidas por 30 dias de acordo com [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: As solicitações são retidas por 30 dias de acordo com [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -90,8 +90,8 @@ OpenCode Zen работает как любой другой провайдер
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -101,6 +101,7 @@ OpenCode Zen работает как любой другой провайдер
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
[идентификатор модели](/docs/config/#models) в вашей конфигурации OpenCode
@@ -128,6 +129,7 @@ https://opencode.ai/zen/v1/models
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -177,6 +179,7 @@ https://opencode.ai/zen/v1/models
- MiniMax M2.5 Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель.
- Ling 2.6 Flash Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель.
- Hy3 Preview Flash Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель.
- Nemotron 3 Super Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель.
- Big Pickle — это скрытая модель, которая доступна бесплатно в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель.
@@ -224,6 +227,7 @@ https://opencode.ai/zen/v1/models
- Big Pickle: во время бесплатного периода собранные данные могут использоваться для улучшения модели.
- MiniMax M2.5 Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели.
- Ling 2.6 Flash Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели.
- Hy3 Preview Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели.
- Nemotron 3 Super Free (бесплатные эндпоинты NVIDIA): предоставляется в соответствии с [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Только для пробного использования, не для продакшена и не для чувствительных данных. NVIDIA логирует запросы и ответы, чтобы улучшать свои модели и сервисы. Не отправляйте персональные или конфиденциальные данные.
- OpenAI APIs: запросы хранятся 30 дней в соответствии с [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: запросы хранятся 30 дней в соответствии с [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -83,8 +83,8 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -94,6 +94,7 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
[model id](/docs/config/#models) ใน OpenCode config ของคุณใช้รูปแบบ `opencode/<model-id>` ตัวอย่างเช่น สำหรับ GPT 5.4 คุณจะใช้ `opencode/gpt-5.4` ใน config ของคุณ
@@ -119,6 +120,7 @@ https://opencode.ai/zen/v1/models
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -168,6 +170,7 @@ https://opencode.ai/zen/v1/models
- MiniMax M2.5 Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล
- Ling 2.6 Flash Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล
- Hy3 Preview Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล
- Nemotron 3 Super Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล
- Big Pickle เป็น stealth model ที่ใช้งานฟรีบน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล
@@ -212,6 +215,7 @@ https://opencode.ai/zen/v1/models
- Big Pickle: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล
- MiniMax M2.5 Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล
- Ling 2.6 Flash Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล
- Hy3 Preview Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล
- Nemotron 3 Super Free (endpoint ฟรีของ NVIDIA): ให้บริการภายใต้ [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) ใช้สำหรับการทดลองเท่านั้น ไม่เหมาะสำหรับ production หรือข้อมูลที่อ่อนไหว NVIDIA จะบันทึก prompt และ output เพื่อนำไปปรับปรุงโมเดลและบริการของตน โปรดอย่าส่งข้อมูลส่วนบุคคลหรือข้อมูลลับ.
- OpenAI APIs: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -81,8 +81,8 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -92,6 +92,7 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
OpenCode yapılandırmanızdaki [model id](/docs/config/#models) `opencode/<model-id>` biçimini kullanır. Örneğin, GPT 5.4 için yapılandırmanızda `opencode/gpt-5.4` kullanırsınız.
@@ -117,6 +118,7 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -166,6 +168,7 @@ Kredi kartı ücretleri maliyet üzerinden yansıtılır (%4.4 + işlem başına
- MiniMax M2.5 Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor.
- Ling 2.6 Flash Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor.
- Hy3 Preview Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor.
- Nemotron 3 Super Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor.
- Big Pickle, sınırlı bir süre için OpenCode'da ücretsiz olan gizli bir modeldir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor.
@@ -210,6 +213,7 @@ Tüm modellerimiz US'de barındırılıyor. Sağlayıcılarımız zero-retention
- Big Pickle: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir.
- MiniMax M2.5 Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir.
- Ling 2.6 Flash Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir.
- Hy3 Preview Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir.
- Nemotron 3 Super Free (ücretsiz NVIDIA uç noktaları): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) kapsamında sunulur. Yalnızca deneme amaçlıdır; üretim veya hassas veriler için uygun değildir. NVIDIA, modellerini ve hizmetlerini geliştirmek için promptları ve çıktıları kaydeder. Kişisel veya gizli veri göndermeyin.
- OpenAI APIs: İstekler [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) uyarınca 30 gün boyunca saklanır.
- Anthropic APIs: İstekler [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) uyarınca 30 gün boyunca saklanır.
+6 -2
View File
@@ -90,8 +90,8 @@ You can also access our models through the following API endpoints.
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -101,6 +101,7 @@ You can also access our models through the following API endpoints.
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
The [model id](/docs/config/#models) in your OpenCode config
@@ -128,6 +129,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -177,6 +179,7 @@ The free models:
- MiniMax M2.5 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
- Ling 2.6 Flash Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
- Hy3 Preview Flash Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
- Nemotron 3 Super Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
- Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model.
@@ -224,6 +227,7 @@ All our models are hosted in the US. Our providers follow a zero-retention polic
- Big Pickle: During its free period, collected data may be used to improve the model.
- MiniMax M2.5 Free: During its free period, collected data may be used to improve the model.
- Ling 2.6 Flash Free: During its free period, collected data may be used to improve the model.
- Hy3 Preview Free: During its free period, collected data may be used to improve the model.
- Nemotron 3 Super Free (NVIDIA free endpoints): Provided under the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Trial use only — not for production or sensitive data. Prompts and outputs are logged by NVIDIA to improve its models and services. Do not submit personal or confidential data.
- OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data).
- Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage).
+6 -2
View File
@@ -81,8 +81,8 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -92,6 +92,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
在你的 OpenCode 配置中,[模型 ID](/docs/config/#models) 使用 `opencode/<model-id>` 格式。例如,对于 GPT 5.4,你需要在配置中使用 `opencode/gpt-5.4`。
@@ -117,6 +118,7 @@ https://opencode.ai/zen/v1/models
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -166,6 +168,7 @@ https://opencode.ai/zen/v1/models
- MiniMax M2.5 Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。
- Ling 2.6 Flash Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。
- Hy3 Preview Flash Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。
- Nemotron 3 Super Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。
- Big Pickle 是一个隐身模型,目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。
@@ -210,6 +213,7 @@ https://opencode.ai/zen/v1/models
- Big Pickle:在免费期间,收集的数据可能会被用于改进模型。
- MiniMax M2.5 Free:在免费期间,收集的数据可能会被用于改进模型。
- Ling 2.6 Flash Free:在免费期间,收集的数据可能会被用于改进模型。
- Hy3 Preview Free:在免费期间,收集的数据可能会被用于改进模型。
- Nemotron 3 Super FreeNVIDIA 免费端点):根据 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。仅供试用,不适用于生产环境或敏感数据。NVIDIA 会记录提示词和输出内容,以改进其模型和服务。请勿提交个人或机密数据。
- OpenAI APIs:请求会根据 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。
- Anthropic APIs:请求会根据 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 保留 30 天。
+6 -2
View File
@@ -85,8 +85,8 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` |
| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -96,6 +96,7 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。
| Kimi K2.6 | kimi-k2.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Ling 2.6 Flash | ling-2.6-flash | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Hy3 Preview Free | hy3-preview-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
OpenCode 設定中的 [模型 ID](/docs/config/#models) 會使用 `opencode/<model-id>`
@@ -122,6 +123,7 @@ https://opencode.ai/zen/v1/models
| Big Pickle | Free | Free | Free | - |
| MiniMax M2.5 Free | Free | Free | Free | - |
| Ling 2.6 Flash Free | Free | Free | Free | - |
| Hy3 Preview Free | Free | Free | Free | - |
| Nemotron 3 Super Free | Free | Free | Free | - |
| MiniMax M2.7 | $0.30 | $1.20 | $0.06 | $0.375 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
@@ -172,6 +174,7 @@ https://opencode.ai/zen/v1/models
- MiniMax M2.5 Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。
- Ling 2.6 Flash Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。
- Hy3 Preview Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。
- Nemotron 3 Super Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。
- Big Pickle 是一個隱身模型,在 OpenCode 上限時免費提供。團隊正在利用這段時間收集回饋並改進模型。
@@ -217,6 +220,7 @@ https://opencode.ai/zen/v1/models
- Big Pickle: 在免費期間,收集到的資料可能會用於改進模型。
- MiniMax M2.5 Free: 在免費期間,收集到的資料可能會用於改進模型。
- Ling 2.6 Flash Free: 在免費期間,收集到的資料可能會用於改進模型。
- Hy3 Preview Free: 在免費期間,收集到的資料可能會用於改進模型。
- Nemotron 3 Super FreeNVIDIA 免費端點):依據 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。僅供試用,不適用於正式環境或敏感資料。NVIDIA 會記錄提示詞與輸出內容,以改進其模型與服務。請勿提交個人或機密資料。
- OpenAI APIs: 請求會依據 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。
- Anthropic APIs: 請求會依據 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 保留 30 天。
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "1.14.21",
"version": "1.14.22",
"publisher": "sst-dev",
"repository": {
"type": "git",