mirror of
https://github.com/anomalyco/opencode.git
synced 2026-03-07 23:23:57 +00:00
Compare commits
31 Commits
opencode/h
...
v1.2.20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c7d968c44 | ||
|
|
326c70184d | ||
|
|
aec6ca71fa | ||
|
|
c04da45be5 | ||
|
|
74effa8eec | ||
|
|
cb411248bf | ||
|
|
46d7d2fdc0 | ||
|
|
d68afcaa55 | ||
|
|
bf35a865ba | ||
|
|
6733a5a822 | ||
|
|
7e28098365 | ||
|
|
ae5c9ed3dd | ||
|
|
a9bf1c0505 | ||
|
|
dad248832d | ||
|
|
6e89d3e597 | ||
|
|
3ebba02d04 | ||
|
|
cf425d114e | ||
|
|
39691e5174 | ||
|
|
adaee66364 | ||
|
|
a6978167ae | ||
|
|
80c36c788c | ||
|
|
76cdc668e8 | ||
|
|
2ba1ecabc9 | ||
|
|
e3b6d84b57 | ||
|
|
0638e49b02 | ||
|
|
2c58964a6b | ||
|
|
9507b0eace | ||
|
|
4da199697b | ||
|
|
9cccaa693a | ||
|
|
bb37e908ad | ||
|
|
d802e28381 |
52
bun.lock
52
bun.lock
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -47,7 +47,7 @@
|
||||
"@thisbeyond/solid-dnd": "0.7.5",
|
||||
"diff": "catalog:",
|
||||
"fuzzysort": "catalog:",
|
||||
"ghostty-web": "0.4.0",
|
||||
"ghostty-web": "github:anomalyco/ghostty-web#main",
|
||||
"luxon": "catalog:",
|
||||
"marked": "catalog:",
|
||||
"marked-shiki": "catalog:",
|
||||
@@ -76,7 +76,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -110,7 +110,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -137,7 +137,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -161,7 +161,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -185,7 +185,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -218,7 +218,7 @@
|
||||
},
|
||||
"packages/desktop-electron": {
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -248,7 +248,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -277,7 +277,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -293,7 +293,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -373,6 +373,7 @@
|
||||
"ulid": "catalog:",
|
||||
"vscode-jsonrpc": "8.2.1",
|
||||
"web-tree-sitter": "0.25.10",
|
||||
"which": "6.0.1",
|
||||
"xdg-basedir": "5.1.0",
|
||||
"yargs": "18.0.0",
|
||||
"zod": "catalog:",
|
||||
@@ -395,6 +396,7 @@
|
||||
"@types/bun": "catalog:",
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/turndown": "5.0.5",
|
||||
"@types/which": "3.0.4",
|
||||
"@types/yargs": "17.0.33",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"drizzle-kit": "1.0.0-beta.12-a5629fb",
|
||||
@@ -407,7 +409,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -427,7 +429,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.90.10",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -438,7 +440,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -473,7 +475,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -519,7 +521,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -530,7 +532,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -2120,6 +2122,8 @@
|
||||
|
||||
"@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
|
||||
|
||||
"@types/which": ["@types/which@3.0.4", "", {}, "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||
|
||||
"@types/yargs": ["@types/yargs@17.0.33", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA=="],
|
||||
@@ -2958,7 +2962,7 @@
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
|
||||
|
||||
"ghostty-web": ["ghostty-web@0.4.0", "", {}, "sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg=="],
|
||||
"ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#4af877d", {}, "anomalyco-ghostty-web-4af877d", "sha512-fbEK8mtr7ar4ySsF+JUGjhaZrane7dKphanN+SxHt5XXI6yLMAh/Hpf6sNCOyyVa2UlGCd7YpXG/T2v2RUAX+A=="],
|
||||
|
||||
"gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
|
||||
|
||||
@@ -3236,7 +3240,7 @@
|
||||
|
||||
"isbinaryfile": ["isbinaryfile@5.0.7", "", {}, "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ=="],
|
||||
|
||||
"isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
|
||||
"isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="],
|
||||
|
||||
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
|
||||
|
||||
@@ -4586,7 +4590,7 @@
|
||||
|
||||
"when-exit": ["when-exit@2.1.5", "", {}, "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="],
|
||||
|
||||
"which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
|
||||
"which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
|
||||
|
||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||
|
||||
@@ -5202,6 +5206,8 @@
|
||||
|
||||
"app-builder-lib/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="],
|
||||
|
||||
"app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
|
||||
|
||||
"archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
@@ -5388,6 +5394,8 @@
|
||||
|
||||
"node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="],
|
||||
|
||||
"node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
|
||||
|
||||
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||
|
||||
"nypm/citty": ["citty@0.2.1", "", {}, "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg=="],
|
||||
@@ -5918,6 +5926,8 @@
|
||||
|
||||
"app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
|
||||
|
||||
"archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"archiver-utils/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
@@ -6000,6 +6010,8 @@
|
||||
|
||||
"node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="],
|
||||
|
||||
"node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
|
||||
|
||||
"opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||
|
||||
"opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { Context as GitHubContext } from "@actions/github/lib/context"
|
||||
import type { IssueCommentEvent, PullRequestReviewCommentEvent } from "@octokit/webhooks-types"
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk"
|
||||
import { spawn } from "node:child_process"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
type GitHubAuthor = {
|
||||
login: string
|
||||
@@ -281,7 +282,7 @@ async function assertOpencodeConnected() {
|
||||
connected = true
|
||||
break
|
||||
} catch (e) {}
|
||||
await Bun.sleep(300)
|
||||
await sleep(300)
|
||||
} while (retry++ < 30)
|
||||
|
||||
if (!connected) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-ZmxeRNy2chc9py4m1iW6B+c/NSccMnVZ0lfni/EMdHw=",
|
||||
"aarch64-linux": "sha256-R+1mxsmAQicerN8ixVy0ff6V8bZ4GH18MHpihvWnaTg=",
|
||||
"aarch64-darwin": "sha256-m+QT20ohlqo9e86qXu67eKthZm6VDRLwlqJ9CNlEV+0=",
|
||||
"x86_64-darwin": "sha256-4GeNPyTT2Hq4rxHGSON23ul5Ud3yFGE0QUVsB03Gidc="
|
||||
"x86_64-linux": "sha256-pBTIT8Pgdm3272YhBjiAZsmj0SSpHTklh6lGc8YcMoE=",
|
||||
"aarch64-linux": "sha256-prt039++d5UZgtldAN6+RVOR557ifIeusiy5XpzN8QU=",
|
||||
"aarch64-darwin": "sha256-Y3f+cXcIGLqz6oyc5fG22t6CLD4wGkvwqO6RNXjFriQ=",
|
||||
"x86_64-darwin": "sha256-BjbBBhQUgGhrlP56skABcrObvutNUZSWnrnPCg1OTKE="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +197,7 @@ export async function createTestProject() {
|
||||
await fs.writeFile(path.join(root, "README.md"), "# e2e\n")
|
||||
|
||||
execSync("git init", { cwd: root, stdio: "ignore" })
|
||||
execSync("git config core.fsmonitor false", { cwd: root, stdio: "ignore" })
|
||||
execSync("git add -A", { cwd: root, stdio: "ignore" })
|
||||
execSync('git -c user.name="e2e" -c user.email="e2e@example.com" commit -m "init" --allow-empty', {
|
||||
cwd: root,
|
||||
@@ -207,7 +208,10 @@ export async function createTestProject() {
|
||||
}
|
||||
|
||||
export async function cleanupTestProject(directory: string) {
|
||||
await fs.rm(directory, { recursive: true, force: true }).catch(() => undefined)
|
||||
try {
|
||||
execSync("git fsmonitor--daemon stop", { cwd: directory, stdio: "ignore" })
|
||||
} catch {}
|
||||
await fs.rm(directory, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }).catch(() => undefined)
|
||||
}
|
||||
|
||||
export function sessionIDFromUrl(url: string) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@@ -57,7 +57,7 @@
|
||||
"@thisbeyond/solid-dnd": "0.7.5",
|
||||
"diff": "catalog:",
|
||||
"fuzzysort": "catalog:",
|
||||
"ghostty-web": "0.4.0",
|
||||
"ghostty-web": "github:anomalyco/ghostty-web#main",
|
||||
"luxon": "catalog:",
|
||||
"marked": "catalog:",
|
||||
"marked-shiki": "catalog:",
|
||||
|
||||
@@ -22,7 +22,7 @@ import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { getFilename } from "@opencode-ai/util/path"
|
||||
@@ -1937,20 +1937,14 @@ export default function Layout(props: ParentProps) {
|
||||
fallback={
|
||||
<>
|
||||
<div class="shrink-0 py-4 px-3">
|
||||
<TooltipKeybind
|
||||
title={language.t("command.session.new")}
|
||||
keybind={command.keybind("session.new")}
|
||||
placement="top"
|
||||
<Button
|
||||
size="large"
|
||||
icon="plus-small"
|
||||
class="w-full"
|
||||
onClick={() => navigateWithSidebarReset(`/${base64Encode(p.worktree)}/session`)}
|
||||
>
|
||||
<Button
|
||||
size="large"
|
||||
icon="plus-small"
|
||||
class="w-full"
|
||||
onClick={() => navigateWithSidebarReset(`/${base64Encode(p.worktree)}/session`)}
|
||||
>
|
||||
{language.t("command.session.new")}
|
||||
</Button>
|
||||
</TooltipKeybind>
|
||||
{language.t("command.session.new")}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex-1 min-h-0">
|
||||
<LocalWorkspace
|
||||
@@ -1965,15 +1959,9 @@ export default function Layout(props: ParentProps) {
|
||||
>
|
||||
<>
|
||||
<div class="shrink-0 py-4 px-3">
|
||||
<TooltipKeybind
|
||||
title={language.t("workspace.new")}
|
||||
keybind={command.keybind("workspace.new")}
|
||||
placement="top"
|
||||
>
|
||||
<Button size="large" icon="plus-small" class="w-full" onClick={() => createWorkspace(p)}>
|
||||
{language.t("workspace.new")}
|
||||
</Button>
|
||||
</TooltipKeybind>
|
||||
<Button size="large" icon="plus-small" class="w-full" onClick={() => createWorkspace(p)}>
|
||||
{language.t("workspace.new")}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="relative flex-1 min-h-0">
|
||||
<DragDropProvider
|
||||
|
||||
@@ -720,6 +720,7 @@ export default function Page() {
|
||||
showAllFiles,
|
||||
tabForPath: file.tab,
|
||||
openTab: tabs().open,
|
||||
setActive: tabs().setActive,
|
||||
loadFile: file.load,
|
||||
})
|
||||
|
||||
|
||||
@@ -11,12 +11,13 @@ describe("createOpenReviewFile", () => {
|
||||
return `file://${path}`
|
||||
},
|
||||
openTab: (tab) => calls.push(`open:${tab}`),
|
||||
setActive: (tab) => calls.push(`active:${tab}`),
|
||||
loadFile: (path) => calls.push(`load:${path}`),
|
||||
})
|
||||
|
||||
openReviewFile("src/a.ts")
|
||||
|
||||
expect(calls).toEqual(["show", "load:src/a.ts", "tab:src/a.ts", "open:file://src/a.ts"])
|
||||
expect(calls).toEqual(["show", "load:src/a.ts", "tab:src/a.ts", "open:file://src/a.ts", "active:file://src/a.ts"])
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -24,15 +24,20 @@ export const createOpenReviewFile = (input: {
|
||||
showAllFiles: () => void
|
||||
tabForPath: (path: string) => string
|
||||
openTab: (tab: string) => void
|
||||
setActive: (tab: string) => void
|
||||
loadFile: (path: string) => any | Promise<void>
|
||||
}) => {
|
||||
return (path: string) => {
|
||||
batch(() => {
|
||||
input.showAllFiles()
|
||||
const maybePromise = input.loadFile(path)
|
||||
const openTab = () => input.openTab(input.tabForPath(path))
|
||||
if (maybePromise instanceof Promise) maybePromise.then(openTab)
|
||||
else openTab()
|
||||
const open = () => {
|
||||
const tab = input.tabForPath(path)
|
||||
input.openTab(tab)
|
||||
input.setActive(tab)
|
||||
}
|
||||
if (maybePromise instanceof Promise) maybePromise.then(open)
|
||||
else open()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { For, createEffect, createMemo, on, onCleanup, Show, startTransition, Index, type JSX } from "solid-js"
|
||||
import { For, createEffect, createMemo, on, onCleanup, Show, Index, type JSX } from "solid-js"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { useNavigate, useParams } from "@solidjs/router"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
@@ -160,7 +160,7 @@ function createTimelineStaging(input: TimelineStageInput) {
|
||||
}
|
||||
const currentTotal = input.messages().length
|
||||
count = Math.min(currentTotal, count + input.config.batch)
|
||||
startTransition(() => setState("count", count))
|
||||
setState("count", count)
|
||||
if (count >= currentTotal) {
|
||||
setState({ completedSession: sessionKey, activeSession: "" })
|
||||
frame = undefined
|
||||
|
||||
@@ -22,6 +22,5 @@
|
||||
}
|
||||
},
|
||||
"include": ["src", "package.json"],
|
||||
"exclude": ["dist", "ts-dist"],
|
||||
"references": [{ "path": "../sdk/js" }]
|
||||
"exclude": ["dist", "ts-dist"]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -108,6 +108,26 @@ const DOCS_SEGMENT = new Set([
|
||||
"zh-tw",
|
||||
])
|
||||
|
||||
const DOCS_LOCALE = {
|
||||
ar: "ar",
|
||||
da: "da",
|
||||
de: "de",
|
||||
en: "en",
|
||||
es: "es",
|
||||
fr: "fr",
|
||||
it: "it",
|
||||
ja: "ja",
|
||||
ko: "ko",
|
||||
nb: "no",
|
||||
"pt-br": "br",
|
||||
root: "en",
|
||||
ru: "ru",
|
||||
th: "th",
|
||||
tr: "tr",
|
||||
"zh-cn": "zh",
|
||||
"zh-tw": "zht",
|
||||
} as const satisfies Record<string, Locale>
|
||||
|
||||
function suffix(pathname: string) {
|
||||
const index = pathname.search(/[?#]/)
|
||||
if (index === -1) {
|
||||
@@ -130,7 +150,12 @@ export function docs(locale: Locale, pathname: string) {
|
||||
return `${next.path}${next.suffix}`
|
||||
}
|
||||
|
||||
if (value === "root") return `${next.path}${next.suffix}`
|
||||
if (value === "root") {
|
||||
if (next.path === "/docs/en") return `/docs${next.suffix}`
|
||||
if (next.path === "/docs/en/") return `/docs/${next.suffix}`
|
||||
if (next.path.startsWith("/docs/en/")) return `/docs/${next.path.slice("/docs/en/".length)}${next.suffix}`
|
||||
return `${next.path}${next.suffix}`
|
||||
}
|
||||
|
||||
if (next.path === "/docs") return `/docs/${value}${next.suffix}`
|
||||
if (next.path === "/docs/") return `/docs/${value}/${next.suffix}`
|
||||
@@ -154,6 +179,15 @@ export function fromPathname(pathname: string) {
|
||||
return parseLocale(fix(pathname).split("/")[1])
|
||||
}
|
||||
|
||||
export function fromDocsPathname(pathname: string) {
|
||||
const next = fix(pathname)
|
||||
const value = next.split("/")[2]?.toLowerCase()
|
||||
if (!value) return null
|
||||
if (!next.startsWith("/docs/")) return null
|
||||
if (!(value in DOCS_LOCALE)) return null
|
||||
return DOCS_LOCALE[value as keyof typeof DOCS_LOCALE]
|
||||
}
|
||||
|
||||
export function strip(pathname: string) {
|
||||
const locale = fromPathname(pathname)
|
||||
if (!locale) return fix(pathname)
|
||||
@@ -272,6 +306,9 @@ export function localeFromRequest(request: Request) {
|
||||
const fromPath = fromPathname(new URL(request.url).pathname)
|
||||
if (fromPath) return fromPath
|
||||
|
||||
const fromDocsPath = fromDocsPathname(new URL(request.url).pathname)
|
||||
if (fromDocsPath) return fromDocsPath
|
||||
|
||||
return (
|
||||
localeFromCookieHeader(request.headers.get("cookie")) ??
|
||||
detectFromAcceptLanguage(request.headers.get("accept-language"))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { Resource } from "@opencode-ai/console-resource"
|
||||
import { docs, localeFromRequest, tag } from "~/lib/language"
|
||||
import { cookie, docs, localeFromRequest, tag } from "~/lib/language"
|
||||
|
||||
async function handler(evt: APIEvent) {
|
||||
const req = evt.request.clone()
|
||||
@@ -17,7 +17,9 @@ async function handler(evt: APIEvent) {
|
||||
headers,
|
||||
body: req.body,
|
||||
})
|
||||
return response
|
||||
const next = new Response(response.body, response)
|
||||
next.headers.append("set-cookie", cookie(locale))
|
||||
return next
|
||||
}
|
||||
|
||||
export const GET = handler
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { Resource } from "@opencode-ai/console-resource"
|
||||
import { docs, localeFromRequest, tag } from "~/lib/language"
|
||||
import { cookie, docs, localeFromRequest, tag } from "~/lib/language"
|
||||
|
||||
async function handler(evt: APIEvent) {
|
||||
const req = evt.request.clone()
|
||||
@@ -17,7 +17,9 @@ async function handler(evt: APIEvent) {
|
||||
headers,
|
||||
body: req.body,
|
||||
})
|
||||
return response
|
||||
const next = new Response(response.body, response)
|
||||
next.headers.append("set-cookie", cookie(locale))
|
||||
return next
|
||||
}
|
||||
|
||||
export const GET = handler
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { APIEvent } from "@solidjs/start/server"
|
||||
import { Resource } from "@opencode-ai/console-resource"
|
||||
import { docs, localeFromRequest, tag } from "~/lib/language"
|
||||
import { cookie, docs, localeFromRequest, tag } from "~/lib/language"
|
||||
|
||||
async function handler(evt: APIEvent) {
|
||||
const req = evt.request.clone()
|
||||
@@ -17,7 +17,9 @@ async function handler(evt: APIEvent) {
|
||||
headers,
|
||||
body: req.body,
|
||||
})
|
||||
return response
|
||||
const next = new Response(response.body, response)
|
||||
next.headers.append("set-cookie", cookie(locale))
|
||||
return next
|
||||
}
|
||||
|
||||
export const GET = handler
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"private": true,
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.2.17"
|
||||
version = "1.2.20"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/anomalyco/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.17/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.20/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.17/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.20/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.17/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.20/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.17/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.20/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.17/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.20/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -43,6 +43,7 @@
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/turndown": "5.0.5",
|
||||
"@types/yargs": "17.0.33",
|
||||
"@types/which": "3.0.4",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"drizzle-kit": "1.0.0-beta.12-a5629fb",
|
||||
"drizzle-orm": "1.0.0-beta.12-a5629fb",
|
||||
@@ -127,6 +128,7 @@
|
||||
"ulid": "catalog:",
|
||||
"vscode-jsonrpc": "8.2.1",
|
||||
"web-tree-sitter": "0.25.10",
|
||||
"which": "6.0.1",
|
||||
"xdg-basedir": "5.1.0",
|
||||
"yargs": "18.0.0",
|
||||
"zod": "catalog:",
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
import { Log } from "../util/log"
|
||||
import { pathToFileURL } from "bun"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Hash } from "../util/hash"
|
||||
import { ACPSessionManager } from "./session"
|
||||
import type { ACPConfig } from "./types"
|
||||
import { Provider } from "../provider/provider"
|
||||
@@ -281,7 +282,7 @@ export namespace ACP {
|
||||
const output = this.bashOutput(part)
|
||||
const content: ToolCallContent[] = []
|
||||
if (output) {
|
||||
const hash = String(Bun.hash(output))
|
||||
const hash = Hash.fast(output)
|
||||
if (part.tool === "bash") {
|
||||
if (this.bashSnapshots.get(part.callID) === hash) {
|
||||
await this.connection
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Instance } from "../../project/instance"
|
||||
import type { Hooks } from "@opencode-ai/plugin"
|
||||
import { Process } from "../../util/process"
|
||||
import { text } from "node:stream/consumers"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
type PluginAuth = NonNullable<Hooks["auth"]>
|
||||
|
||||
@@ -47,7 +48,7 @@ async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string,
|
||||
const method = plugin.auth.methods[index]
|
||||
|
||||
// Handle prompts for all auth types
|
||||
await Bun.sleep(10)
|
||||
await sleep(10)
|
||||
const inputs: Record<string, string> = {}
|
||||
if (method.prompts) {
|
||||
for (const prompt of method.prompts) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { bootstrap } from "../../bootstrap"
|
||||
import { cmd } from "../cmd"
|
||||
import { Log } from "../../../util/log"
|
||||
import { EOL } from "os"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
export const LSPCommand = cmd({
|
||||
command: "lsp",
|
||||
@@ -19,7 +20,7 @@ const DiagnosticsCommand = cmd({
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
await LSP.touchFile(args.file, true)
|
||||
await Bun.sleep(1000)
|
||||
await sleep(1000)
|
||||
process.stdout.write(JSON.stringify(await LSP.diagnostics(), null, 2) + EOL)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -28,6 +28,7 @@ import { Bus } from "../../bus"
|
||||
import { MessageV2 } from "../../session/message-v2"
|
||||
import { SessionPrompt } from "@/session/prompt"
|
||||
import { $ } from "bun"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
type GitHubAuthor = {
|
||||
login: string
|
||||
@@ -353,7 +354,7 @@ export const GithubInstallCommand = cmd({
|
||||
}
|
||||
|
||||
retries++
|
||||
await Bun.sleep(1000)
|
||||
await sleep(1000)
|
||||
} while (true)
|
||||
|
||||
s.stop("Installed GitHub app")
|
||||
@@ -1372,7 +1373,7 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
||||
} catch (e) {
|
||||
if (retries > 0) {
|
||||
console.log(`Retrying after ${delayMs}ms...`)
|
||||
await Bun.sleep(delayMs)
|
||||
await sleep(delayMs)
|
||||
return withRetry(fn, retries - 1, delayMs)
|
||||
}
|
||||
throw e
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Filesystem } from "../../util/filesystem"
|
||||
import { Process } from "../../util/process"
|
||||
import { EOL } from "os"
|
||||
import path from "path"
|
||||
import { which } from "../../util/which"
|
||||
|
||||
function pagerCmd(): string[] {
|
||||
const lessOptions = ["-R", "-S"]
|
||||
@@ -17,7 +18,7 @@ function pagerCmd(): string[] {
|
||||
}
|
||||
|
||||
// user could have less installed via other options
|
||||
const lessOnPath = Bun.which("less")
|
||||
const lessOnPath = which("less")
|
||||
if (lessOnPath) {
|
||||
if (Filesystem.stat(lessOnPath)?.size) return [lessOnPath, ...lessOptions]
|
||||
}
|
||||
@@ -27,7 +28,7 @@ function pagerCmd(): string[] {
|
||||
if (Filesystem.stat(less)?.size) return [less, ...lessOptions]
|
||||
}
|
||||
|
||||
const git = Bun.which("git")
|
||||
const git = which("git")
|
||||
if (git) {
|
||||
const less = path.join(git, "..", "..", "usr", "bin", "less.exe")
|
||||
if (Filesystem.stat(less)?.size) return [less, ...lessOptions]
|
||||
|
||||
@@ -1625,11 +1625,14 @@ function InlineTool(props: {
|
||||
spinner?: boolean
|
||||
children: JSX.Element
|
||||
part: ToolPart
|
||||
onClick?: () => void
|
||||
}) {
|
||||
const [margin, setMargin] = createSignal(0)
|
||||
const { theme } = useTheme()
|
||||
const ctx = use()
|
||||
const sync = useSync()
|
||||
const renderer = useRenderer()
|
||||
const [hover, setHover] = createSignal(false)
|
||||
|
||||
const permission = createMemo(() => {
|
||||
const callID = sync.data.permission[ctx.sessionID]?.at(0)?.tool?.callID
|
||||
@@ -1639,6 +1642,7 @@ function InlineTool(props: {
|
||||
|
||||
const fg = createMemo(() => {
|
||||
if (permission()) return theme.warning
|
||||
if (hover() && props.onClick) return theme.text
|
||||
if (props.complete) return theme.textMuted
|
||||
return theme.text
|
||||
})
|
||||
@@ -1656,6 +1660,12 @@ function InlineTool(props: {
|
||||
<box
|
||||
marginTop={margin()}
|
||||
paddingLeft={3}
|
||||
onMouseOver={() => props.onClick && setHover(true)}
|
||||
onMouseOut={() => setHover(false)}
|
||||
onMouseUp={() => {
|
||||
if (renderer.getSelection()?.getSelectedText()) return
|
||||
props.onClick?.()
|
||||
}}
|
||||
renderBefore={function () {
|
||||
const el = this as BoxRenderable
|
||||
const parent = el.parent
|
||||
@@ -1999,6 +2009,11 @@ function Task(props: ToolProps<typeof TaskTool>) {
|
||||
complete={props.input.description}
|
||||
pending="Delegating..."
|
||||
part={props.part}
|
||||
onClick={() => {
|
||||
if (props.metadata.sessionId) {
|
||||
navigate({ type: "session", sessionID: props.metadata.sessionId })
|
||||
}
|
||||
}}
|
||||
>
|
||||
{content()}
|
||||
</InlineTool>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { tmpdir } from "os"
|
||||
import path from "path"
|
||||
import { Filesystem } from "../../../../util/filesystem"
|
||||
import { Process } from "../../../../util/process"
|
||||
import { which } from "../../../../util/which"
|
||||
|
||||
/**
|
||||
* Writes text to clipboard via OSC 52 escape sequence.
|
||||
@@ -76,7 +77,7 @@ export namespace Clipboard {
|
||||
const getCopyMethod = lazy(() => {
|
||||
const os = platform()
|
||||
|
||||
if (os === "darwin" && Bun.which("osascript")) {
|
||||
if (os === "darwin" && which("osascript")) {
|
||||
console.log("clipboard: using osascript")
|
||||
return async (text: string) => {
|
||||
const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
||||
@@ -85,7 +86,7 @@ export namespace Clipboard {
|
||||
}
|
||||
|
||||
if (os === "linux") {
|
||||
if (process.env["WAYLAND_DISPLAY"] && Bun.which("wl-copy")) {
|
||||
if (process.env["WAYLAND_DISPLAY"] && which("wl-copy")) {
|
||||
console.log("clipboard: using wl-copy")
|
||||
return async (text: string) => {
|
||||
const proc = Process.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" })
|
||||
@@ -95,7 +96,7 @@ export namespace Clipboard {
|
||||
await proc.exited.catch(() => {})
|
||||
}
|
||||
}
|
||||
if (Bun.which("xclip")) {
|
||||
if (which("xclip")) {
|
||||
console.log("clipboard: using xclip")
|
||||
return async (text: string) => {
|
||||
const proc = Process.spawn(["xclip", "-selection", "clipboard"], {
|
||||
@@ -109,7 +110,7 @@ export namespace Clipboard {
|
||||
await proc.exited.catch(() => {})
|
||||
}
|
||||
}
|
||||
if (Bun.which("xsel")) {
|
||||
if (which("xsel")) {
|
||||
console.log("clipboard: using xsel")
|
||||
return async (text: string) => {
|
||||
const proc = Process.spawn(["xsel", "--clipboard", "--input"], {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { GlobalBus } from "@/bus/global"
|
||||
import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2"
|
||||
import type { BunWebSocketData } from "hono/bun"
|
||||
import { Flag } from "@/flag/flag"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
await Log.init({
|
||||
print: process.argv.includes("--print-logs"),
|
||||
@@ -75,7 +76,7 @@ const startEventStream = (directory: string) => {
|
||||
).catch(() => undefined)
|
||||
|
||||
if (!events) {
|
||||
await Bun.sleep(250)
|
||||
await sleep(250)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -84,7 +85,7 @@ const startEventStream = (directory: string) => {
|
||||
}
|
||||
|
||||
if (!signal.aborted) {
|
||||
await Bun.sleep(250)
|
||||
await sleep(250)
|
||||
}
|
||||
}
|
||||
})().catch((error) => {
|
||||
|
||||
@@ -25,12 +25,12 @@ export namespace UI {
|
||||
|
||||
export function println(...message: string[]) {
|
||||
print(...message)
|
||||
Bun.stderr.write(EOL)
|
||||
process.stderr.write(EOL)
|
||||
}
|
||||
|
||||
export function print(...message: string[]) {
|
||||
blank = false
|
||||
Bun.stderr.write(message.join(" "))
|
||||
process.stderr.write(message.join(" "))
|
||||
}
|
||||
|
||||
let blank = false
|
||||
@@ -44,7 +44,7 @@ export namespace UI {
|
||||
const result: string[] = []
|
||||
const reset = "\x1b[0m"
|
||||
const left = {
|
||||
fg: Bun.color("gray", "ansi") ?? "",
|
||||
fg: "\x1b[90m",
|
||||
shadow: "\x1b[38;5;235m",
|
||||
bg: "\x1b[48;5;235m",
|
||||
}
|
||||
|
||||
@@ -1240,7 +1240,7 @@ export namespace Config {
|
||||
if (!parsed.data.$schema && isFile) {
|
||||
parsed.data.$schema = "https://opencode.ai/config.json"
|
||||
const updated = original.replace(/^\s*\{/, '{\n "$schema": "https://opencode.ai/config.json",')
|
||||
await Bun.write(options.path, updated).catch(() => {})
|
||||
await Filesystem.write(options.path, updated).catch(() => {})
|
||||
}
|
||||
const data = parsed.data
|
||||
if (data.plugin && isFile) {
|
||||
@@ -1401,3 +1401,5 @@ export namespace Config {
|
||||
return state().then((x) => x.directories)
|
||||
}
|
||||
}
|
||||
Filesystem.write
|
||||
Filesystem.write
|
||||
|
||||
@@ -70,7 +70,7 @@ export async function migrateTuiConfig(input: MigrateInput) {
|
||||
if (extracted.keybinds !== undefined) payload.keybinds = extracted.keybinds
|
||||
if (tui) Object.assign(payload, tui)
|
||||
|
||||
const wrote = await Bun.write(target, JSON.stringify(payload, null, 2))
|
||||
const wrote = await Filesystem.write(target, JSON.stringify(payload, null, 2))
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
log.warn("failed to write tui migration target", { from: file, to: target, error })
|
||||
@@ -104,7 +104,7 @@ async function backupAndStripLegacy(file: string, source: string) {
|
||||
const hasBackup = await Filesystem.exists(backup)
|
||||
const backed = hasBackup
|
||||
? true
|
||||
: await Bun.write(backup, source)
|
||||
: await Filesystem.write(backup, source)
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
log.warn("failed to backup source config during tui migration", { path: file, backup, error })
|
||||
@@ -123,7 +123,7 @@ async function backupAndStripLegacy(file: string, source: string) {
|
||||
return applyEdits(acc, edits)
|
||||
}, source)
|
||||
|
||||
return Bun.write(file, text)
|
||||
return Filesystem.write(file, text)
|
||||
.then(() => {
|
||||
log.info("stripped tui keys from server config", { path: file, backup })
|
||||
return true
|
||||
|
||||
@@ -418,7 +418,7 @@ export namespace File {
|
||||
const project = Instance.project
|
||||
if (project.vcs !== "git") return []
|
||||
|
||||
const diffOutput = await $`git -c core.quotepath=false diff --numstat HEAD`
|
||||
const diffOutput = await $`git -c core.fsmonitor=false -c core.quotepath=false diff --numstat HEAD`
|
||||
.cwd(Instance.directory)
|
||||
.quiet()
|
||||
.nothrow()
|
||||
@@ -439,11 +439,12 @@ export namespace File {
|
||||
}
|
||||
}
|
||||
|
||||
const untrackedOutput = await $`git -c core.quotepath=false ls-files --others --exclude-standard`
|
||||
.cwd(Instance.directory)
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.text()
|
||||
const untrackedOutput =
|
||||
await $`git -c core.fsmonitor=false -c core.quotepath=false ls-files --others --exclude-standard`
|
||||
.cwd(Instance.directory)
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.text()
|
||||
|
||||
if (untrackedOutput.trim()) {
|
||||
const untrackedFiles = untrackedOutput.trim().split("\n")
|
||||
@@ -464,11 +465,12 @@ export namespace File {
|
||||
}
|
||||
|
||||
// Get deleted files
|
||||
const deletedOutput = await $`git -c core.quotepath=false diff --name-only --diff-filter=D HEAD`
|
||||
.cwd(Instance.directory)
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.text()
|
||||
const deletedOutput =
|
||||
await $`git -c core.fsmonitor=false -c core.quotepath=false diff --name-only --diff-filter=D HEAD`
|
||||
.cwd(Instance.directory)
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.text()
|
||||
|
||||
if (deletedOutput.trim()) {
|
||||
const deletedFiles = deletedOutput.trim().split("\n")
|
||||
@@ -539,8 +541,14 @@ export namespace File {
|
||||
const content = (await Filesystem.readText(full).catch(() => "")).trim()
|
||||
|
||||
if (project.vcs === "git") {
|
||||
let diff = await $`git diff ${file}`.cwd(Instance.directory).quiet().nothrow().text()
|
||||
if (!diff.trim()) diff = await $`git diff --staged ${file}`.cwd(Instance.directory).quiet().nothrow().text()
|
||||
let diff = await $`git -c core.fsmonitor=false diff ${file}`.cwd(Instance.directory).quiet().nothrow().text()
|
||||
if (!diff.trim()) {
|
||||
diff = await $`git -c core.fsmonitor=false diff --staged ${file}`
|
||||
.cwd(Instance.directory)
|
||||
.quiet()
|
||||
.nothrow()
|
||||
.text()
|
||||
}
|
||||
if (diff.trim()) {
|
||||
const original = await $`git show HEAD:${file}`.cwd(Instance.directory).quiet().nothrow().text()
|
||||
const patch = structuredPatch(file, file, original, content, "old", "new", {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { lazy } from "../util/lazy"
|
||||
import { $ } from "bun"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Process } from "../util/process"
|
||||
import { which } from "../util/which"
|
||||
import { text } from "node:stream/consumers"
|
||||
|
||||
import { ZipReader, BlobReader, BlobWriter } from "@zip.js/zip.js"
|
||||
@@ -126,7 +127,7 @@ export namespace Ripgrep {
|
||||
)
|
||||
|
||||
const state = lazy(async () => {
|
||||
const system = Bun.which("rg")
|
||||
const system = which("rg")
|
||||
if (system) {
|
||||
const stat = await fs.stat(system).catch(() => undefined)
|
||||
if (stat?.isFile()) return { filepath: system }
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BunProc } from "../bun"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Process } from "../util/process"
|
||||
import { which } from "../util/which"
|
||||
import { Flag } from "@/flag/flag"
|
||||
|
||||
export interface Info {
|
||||
@@ -18,7 +19,7 @@ export const gofmt: Info = {
|
||||
command: ["gofmt", "-w", "$FILE"],
|
||||
extensions: [".go"],
|
||||
async enabled() {
|
||||
return Bun.which("gofmt") !== null
|
||||
return which("gofmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -27,7 +28,7 @@ export const mix: Info = {
|
||||
command: ["mix", "format", "$FILE"],
|
||||
extensions: [".ex", ".exs", ".eex", ".heex", ".leex", ".neex", ".sface"],
|
||||
async enabled() {
|
||||
return Bun.which("mix") !== null
|
||||
return which("mix") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -152,7 +153,7 @@ export const zig: Info = {
|
||||
command: ["zig", "fmt", "$FILE"],
|
||||
extensions: [".zig", ".zon"],
|
||||
async enabled() {
|
||||
return Bun.which("zig") !== null
|
||||
return which("zig") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -171,7 +172,7 @@ export const ktlint: Info = {
|
||||
command: ["ktlint", "-F", "$FILE"],
|
||||
extensions: [".kt", ".kts"],
|
||||
async enabled() {
|
||||
return Bun.which("ktlint") !== null
|
||||
return which("ktlint") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -180,7 +181,7 @@ export const ruff: Info = {
|
||||
command: ["ruff", "format", "$FILE"],
|
||||
extensions: [".py", ".pyi"],
|
||||
async enabled() {
|
||||
if (!Bun.which("ruff")) return false
|
||||
if (!which("ruff")) return false
|
||||
const configs = ["pyproject.toml", "ruff.toml", ".ruff.toml"]
|
||||
for (const config of configs) {
|
||||
const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree)
|
||||
@@ -210,7 +211,7 @@ export const rlang: Info = {
|
||||
command: ["air", "format", "$FILE"],
|
||||
extensions: [".R"],
|
||||
async enabled() {
|
||||
const airPath = Bun.which("air")
|
||||
const airPath = which("air")
|
||||
if (airPath == null) return false
|
||||
|
||||
try {
|
||||
@@ -239,7 +240,7 @@ export const uvformat: Info = {
|
||||
extensions: [".py", ".pyi"],
|
||||
async enabled() {
|
||||
if (await ruff.enabled()) return false
|
||||
if (Bun.which("uv") !== null) {
|
||||
if (which("uv") !== null) {
|
||||
const proc = Process.spawn(["uv", "format", "--help"], { stderr: "pipe", stdout: "pipe" })
|
||||
const code = await proc.exited
|
||||
return code === 0
|
||||
@@ -253,7 +254,7 @@ export const rubocop: Info = {
|
||||
command: ["rubocop", "--autocorrect", "$FILE"],
|
||||
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
||||
async enabled() {
|
||||
return Bun.which("rubocop") !== null
|
||||
return which("rubocop") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -262,7 +263,7 @@ export const standardrb: Info = {
|
||||
command: ["standardrb", "--fix", "$FILE"],
|
||||
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
||||
async enabled() {
|
||||
return Bun.which("standardrb") !== null
|
||||
return which("standardrb") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -271,7 +272,7 @@ export const htmlbeautifier: Info = {
|
||||
command: ["htmlbeautifier", "$FILE"],
|
||||
extensions: [".erb", ".html.erb"],
|
||||
async enabled() {
|
||||
return Bun.which("htmlbeautifier") !== null
|
||||
return which("htmlbeautifier") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -280,7 +281,7 @@ export const dart: Info = {
|
||||
command: ["dart", "format", "$FILE"],
|
||||
extensions: [".dart"],
|
||||
async enabled() {
|
||||
return Bun.which("dart") !== null
|
||||
return which("dart") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -289,7 +290,7 @@ export const ocamlformat: Info = {
|
||||
command: ["ocamlformat", "-i", "$FILE"],
|
||||
extensions: [".ml", ".mli"],
|
||||
async enabled() {
|
||||
if (!Bun.which("ocamlformat")) return false
|
||||
if (!which("ocamlformat")) return false
|
||||
const items = await Filesystem.findUp(".ocamlformat", Instance.directory, Instance.worktree)
|
||||
return items.length > 0
|
||||
},
|
||||
@@ -300,7 +301,7 @@ export const terraform: Info = {
|
||||
command: ["terraform", "fmt", "$FILE"],
|
||||
extensions: [".tf", ".tfvars"],
|
||||
async enabled() {
|
||||
return Bun.which("terraform") !== null
|
||||
return which("terraform") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -309,7 +310,7 @@ export const latexindent: Info = {
|
||||
command: ["latexindent", "-w", "-s", "$FILE"],
|
||||
extensions: [".tex"],
|
||||
async enabled() {
|
||||
return Bun.which("latexindent") !== null
|
||||
return which("latexindent") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -318,7 +319,7 @@ export const gleam: Info = {
|
||||
command: ["gleam", "format", "$FILE"],
|
||||
extensions: [".gleam"],
|
||||
async enabled() {
|
||||
return Bun.which("gleam") !== null
|
||||
return which("gleam") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -327,7 +328,7 @@ export const shfmt: Info = {
|
||||
command: ["shfmt", "-w", "$FILE"],
|
||||
extensions: [".sh", ".bash"],
|
||||
async enabled() {
|
||||
return Bun.which("shfmt") !== null
|
||||
return which("shfmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -336,7 +337,7 @@ export const nixfmt: Info = {
|
||||
command: ["nixfmt", "$FILE"],
|
||||
extensions: [".nix"],
|
||||
async enabled() {
|
||||
return Bun.which("nixfmt") !== null
|
||||
return which("nixfmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -345,7 +346,7 @@ export const rustfmt: Info = {
|
||||
command: ["rustfmt", "$FILE"],
|
||||
extensions: [".rs"],
|
||||
async enabled() {
|
||||
return Bun.which("rustfmt") !== null
|
||||
return which("rustfmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -372,7 +373,7 @@ export const ormolu: Info = {
|
||||
command: ["ormolu", "-i", "$FILE"],
|
||||
extensions: [".hs"],
|
||||
async enabled() {
|
||||
return Bun.which("ormolu") !== null
|
||||
return which("ormolu") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -381,7 +382,7 @@ export const cljfmt: Info = {
|
||||
command: ["cljfmt", "fix", "--quiet", "$FILE"],
|
||||
extensions: [".clj", ".cljs", ".cljc", ".edn"],
|
||||
async enabled() {
|
||||
return Bun.which("cljfmt") !== null
|
||||
return which("cljfmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -390,6 +391,6 @@ export const dfmt: Info = {
|
||||
command: ["dfmt", "-i", "$FILE"],
|
||||
extensions: [".d"],
|
||||
async enabled() {
|
||||
return Bun.which("dfmt") !== null
|
||||
return which("dfmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Instance } from "../project/instance"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { Archive } from "../util/archive"
|
||||
import { Process } from "../util/process"
|
||||
import { which } from "../util/which"
|
||||
|
||||
export namespace LSPServer {
|
||||
const log = Log.create({ service: "lsp.server" })
|
||||
@@ -75,7 +76,7 @@ export namespace LSPServer {
|
||||
},
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs"],
|
||||
async spawn(root) {
|
||||
const deno = Bun.which("deno")
|
||||
const deno = which("deno")
|
||||
if (!deno) {
|
||||
log.info("deno not found, please install deno first")
|
||||
return
|
||||
@@ -122,7 +123,7 @@ export namespace LSPServer {
|
||||
extensions: [".vue"],
|
||||
root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
|
||||
async spawn(root) {
|
||||
let binary = Bun.which("vue-language-server")
|
||||
let binary = which("vue-language-server")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
const js = path.join(
|
||||
@@ -260,7 +261,7 @@ export namespace LSPServer {
|
||||
|
||||
let lintBin = await resolveBin(lintTarget)
|
||||
if (!lintBin) {
|
||||
const found = Bun.which("oxlint")
|
||||
const found = which("oxlint")
|
||||
if (found) lintBin = found
|
||||
}
|
||||
|
||||
@@ -281,7 +282,7 @@ export namespace LSPServer {
|
||||
|
||||
let serverBin = await resolveBin(serverTarget)
|
||||
if (!serverBin) {
|
||||
const found = Bun.which("oxc_language_server")
|
||||
const found = which("oxc_language_server")
|
||||
if (found) serverBin = found
|
||||
}
|
||||
if (serverBin) {
|
||||
@@ -332,7 +333,7 @@ export namespace LSPServer {
|
||||
let bin: string | undefined
|
||||
if (await Filesystem.exists(localBin)) bin = localBin
|
||||
if (!bin) {
|
||||
const found = Bun.which("biome")
|
||||
const found = which("biome")
|
||||
if (found) bin = found
|
||||
}
|
||||
|
||||
@@ -368,11 +369,11 @@ export namespace LSPServer {
|
||||
},
|
||||
extensions: [".go"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("gopls", {
|
||||
let bin = which("gopls", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
if (!Bun.which("go")) return
|
||||
if (!which("go")) return
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
|
||||
log.info("installing gopls")
|
||||
@@ -405,12 +406,12 @@ export namespace LSPServer {
|
||||
root: NearestRoot(["Gemfile"]),
|
||||
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("rubocop", {
|
||||
let bin = which("rubocop", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
const ruby = Bun.which("ruby")
|
||||
const gem = Bun.which("gem")
|
||||
const ruby = which("ruby")
|
||||
const gem = which("gem")
|
||||
if (!ruby || !gem) {
|
||||
log.info("Ruby not found, please install Ruby first")
|
||||
return
|
||||
@@ -457,7 +458,7 @@ export namespace LSPServer {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let binary = Bun.which("ty")
|
||||
let binary = which("ty")
|
||||
|
||||
const initialization: Record<string, string> = {}
|
||||
|
||||
@@ -509,7 +510,7 @@ export namespace LSPServer {
|
||||
extensions: [".py", ".pyi"],
|
||||
root: NearestRoot(["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"]),
|
||||
async spawn(root) {
|
||||
let binary = Bun.which("pyright-langserver")
|
||||
let binary = which("pyright-langserver")
|
||||
const args = []
|
||||
if (!binary) {
|
||||
const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js")
|
||||
@@ -563,7 +564,7 @@ export namespace LSPServer {
|
||||
extensions: [".ex", ".exs"],
|
||||
root: NearestRoot(["mix.exs", "mix.lock"]),
|
||||
async spawn(root) {
|
||||
let binary = Bun.which("elixir-ls")
|
||||
let binary = which("elixir-ls")
|
||||
if (!binary) {
|
||||
const elixirLsPath = path.join(Global.Path.bin, "elixir-ls")
|
||||
binary = path.join(
|
||||
@@ -574,7 +575,7 @@ export namespace LSPServer {
|
||||
)
|
||||
|
||||
if (!(await Filesystem.exists(binary))) {
|
||||
const elixir = Bun.which("elixir")
|
||||
const elixir = which("elixir")
|
||||
if (!elixir) {
|
||||
log.error("elixir is required to run elixir-ls")
|
||||
return
|
||||
@@ -625,12 +626,12 @@ export namespace LSPServer {
|
||||
extensions: [".zig", ".zon"],
|
||||
root: NearestRoot(["build.zig"]),
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("zls", {
|
||||
let bin = which("zls", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
if (!bin) {
|
||||
const zig = Bun.which("zig")
|
||||
const zig = which("zig")
|
||||
if (!zig) {
|
||||
log.error("Zig is required to use zls. Please install Zig first.")
|
||||
return
|
||||
@@ -737,11 +738,11 @@ export namespace LSPServer {
|
||||
root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"]),
|
||||
extensions: [".cs"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("csharp-ls", {
|
||||
let bin = which("csharp-ls", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
if (!Bun.which("dotnet")) {
|
||||
if (!which("dotnet")) {
|
||||
log.error(".NET SDK is required to install csharp-ls")
|
||||
return
|
||||
}
|
||||
@@ -776,11 +777,11 @@ export namespace LSPServer {
|
||||
root: NearestRoot([".slnx", ".sln", ".fsproj", "global.json"]),
|
||||
extensions: [".fs", ".fsi", ".fsx", ".fsscript"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("fsautocomplete", {
|
||||
let bin = which("fsautocomplete", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
if (!Bun.which("dotnet")) {
|
||||
if (!which("dotnet")) {
|
||||
log.error(".NET SDK is required to install fsautocomplete")
|
||||
return
|
||||
}
|
||||
@@ -817,7 +818,7 @@ export namespace LSPServer {
|
||||
async spawn(root) {
|
||||
// Check if sourcekit-lsp is available in the PATH
|
||||
// This is installed with the Swift toolchain
|
||||
const sourcekit = Bun.which("sourcekit-lsp")
|
||||
const sourcekit = which("sourcekit-lsp")
|
||||
if (sourcekit) {
|
||||
return {
|
||||
process: spawn(sourcekit, {
|
||||
@@ -828,7 +829,7 @@ export namespace LSPServer {
|
||||
|
||||
// If sourcekit-lsp not found, check if xcrun is available
|
||||
// This is specific to macOS where sourcekit-lsp is typically installed with Xcode
|
||||
if (!Bun.which("xcrun")) return
|
||||
if (!which("xcrun")) return
|
||||
|
||||
const lspLoc = await $`xcrun --find sourcekit-lsp`.quiet().nothrow()
|
||||
|
||||
@@ -877,7 +878,7 @@ export namespace LSPServer {
|
||||
},
|
||||
extensions: [".rs"],
|
||||
async spawn(root) {
|
||||
const bin = Bun.which("rust-analyzer")
|
||||
const bin = which("rust-analyzer")
|
||||
if (!bin) {
|
||||
log.info("rust-analyzer not found in path, please install it")
|
||||
return
|
||||
@@ -896,7 +897,7 @@ export namespace LSPServer {
|
||||
extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"],
|
||||
async spawn(root) {
|
||||
const args = ["--background-index", "--clang-tidy"]
|
||||
const fromPath = Bun.which("clangd")
|
||||
const fromPath = which("clangd")
|
||||
if (fromPath) {
|
||||
return {
|
||||
process: spawn(fromPath, args, {
|
||||
@@ -1041,7 +1042,7 @@ export namespace LSPServer {
|
||||
extensions: [".svelte"],
|
||||
root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
|
||||
async spawn(root) {
|
||||
let binary = Bun.which("svelteserver")
|
||||
let binary = which("svelteserver")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
const js = path.join(Global.Path.bin, "node_modules", "svelte-language-server", "bin", "server.js")
|
||||
@@ -1088,7 +1089,7 @@ export namespace LSPServer {
|
||||
}
|
||||
const tsdk = path.dirname(tsserver)
|
||||
|
||||
let binary = Bun.which("astro-ls")
|
||||
let binary = which("astro-ls")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
const js = path.join(Global.Path.bin, "node_modules", "@astrojs", "language-server", "bin", "nodeServer.js")
|
||||
@@ -1132,7 +1133,7 @@ export namespace LSPServer {
|
||||
root: NearestRoot(["pom.xml", "build.gradle", "build.gradle.kts", ".project", ".classpath"]),
|
||||
extensions: [".java"],
|
||||
async spawn(root) {
|
||||
const java = Bun.which("java")
|
||||
const java = which("java")
|
||||
if (!java) {
|
||||
log.error("Java 21 or newer is required to run the JDTLS. Please install it first.")
|
||||
return
|
||||
@@ -1324,7 +1325,7 @@ export namespace LSPServer {
|
||||
extensions: [".yaml", ".yml"],
|
||||
root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]),
|
||||
async spawn(root) {
|
||||
let binary = Bun.which("yaml-language-server")
|
||||
let binary = which("yaml-language-server")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
const js = path.join(
|
||||
@@ -1380,7 +1381,7 @@ export namespace LSPServer {
|
||||
]),
|
||||
extensions: [".lua"],
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("lua-language-server", {
|
||||
let bin = which("lua-language-server", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
@@ -1512,7 +1513,7 @@ export namespace LSPServer {
|
||||
extensions: [".php"],
|
||||
root: NearestRoot(["composer.json", "composer.lock", ".php-version"]),
|
||||
async spawn(root) {
|
||||
let binary = Bun.which("intelephense")
|
||||
let binary = which("intelephense")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
const js = path.join(Global.Path.bin, "node_modules", "intelephense", "lib", "intelephense.js")
|
||||
@@ -1556,7 +1557,7 @@ export namespace LSPServer {
|
||||
extensions: [".prisma"],
|
||||
root: NearestRoot(["schema.prisma", "prisma/schema.prisma", "prisma"], ["package.json"]),
|
||||
async spawn(root) {
|
||||
const prisma = Bun.which("prisma")
|
||||
const prisma = which("prisma")
|
||||
if (!prisma) {
|
||||
log.info("prisma not found, please install prisma")
|
||||
return
|
||||
@@ -1574,7 +1575,7 @@ export namespace LSPServer {
|
||||
extensions: [".dart"],
|
||||
root: NearestRoot(["pubspec.yaml", "analysis_options.yaml"]),
|
||||
async spawn(root) {
|
||||
const dart = Bun.which("dart")
|
||||
const dart = which("dart")
|
||||
if (!dart) {
|
||||
log.info("dart not found, please install dart first")
|
||||
return
|
||||
@@ -1592,7 +1593,7 @@ export namespace LSPServer {
|
||||
extensions: [".ml", ".mli"],
|
||||
root: NearestRoot(["dune-project", "dune-workspace", ".merlin", "opam"]),
|
||||
async spawn(root) {
|
||||
const bin = Bun.which("ocamllsp")
|
||||
const bin = which("ocamllsp")
|
||||
if (!bin) {
|
||||
log.info("ocamllsp not found, please install ocaml-lsp-server")
|
||||
return
|
||||
@@ -1609,7 +1610,7 @@ export namespace LSPServer {
|
||||
extensions: [".sh", ".bash", ".zsh", ".ksh"],
|
||||
root: async () => Instance.directory,
|
||||
async spawn(root) {
|
||||
let binary = Bun.which("bash-language-server")
|
||||
let binary = which("bash-language-server")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
const js = path.join(Global.Path.bin, "node_modules", "bash-language-server", "out", "cli.js")
|
||||
@@ -1648,7 +1649,7 @@ export namespace LSPServer {
|
||||
extensions: [".tf", ".tfvars"],
|
||||
root: NearestRoot([".terraform.lock.hcl", "terraform.tfstate", "*.tf"]),
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("terraform-ls", {
|
||||
let bin = which("terraform-ls", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
@@ -1731,7 +1732,7 @@ export namespace LSPServer {
|
||||
extensions: [".tex", ".bib"],
|
||||
root: NearestRoot([".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot"]),
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("texlab", {
|
||||
let bin = which("texlab", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
@@ -1821,7 +1822,7 @@ export namespace LSPServer {
|
||||
extensions: [".dockerfile", "Dockerfile"],
|
||||
root: async () => Instance.directory,
|
||||
async spawn(root) {
|
||||
let binary = Bun.which("docker-langserver")
|
||||
let binary = which("docker-langserver")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
const js = path.join(Global.Path.bin, "node_modules", "dockerfile-language-server-nodejs", "lib", "server.js")
|
||||
@@ -1860,7 +1861,7 @@ export namespace LSPServer {
|
||||
extensions: [".gleam"],
|
||||
root: NearestRoot(["gleam.toml"]),
|
||||
async spawn(root) {
|
||||
const gleam = Bun.which("gleam")
|
||||
const gleam = which("gleam")
|
||||
if (!gleam) {
|
||||
log.info("gleam not found, please install gleam first")
|
||||
return
|
||||
@@ -1878,9 +1879,9 @@ export namespace LSPServer {
|
||||
extensions: [".clj", ".cljs", ".cljc", ".edn"],
|
||||
root: NearestRoot(["deps.edn", "project.clj", "shadow-cljs.edn", "bb.edn", "build.boot"]),
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("clojure-lsp")
|
||||
let bin = which("clojure-lsp")
|
||||
if (!bin && process.platform === "win32") {
|
||||
bin = Bun.which("clojure-lsp.exe")
|
||||
bin = which("clojure-lsp.exe")
|
||||
}
|
||||
if (!bin) {
|
||||
log.info("clojure-lsp not found, please install clojure-lsp first")
|
||||
@@ -1909,7 +1910,7 @@ export namespace LSPServer {
|
||||
return Instance.directory
|
||||
},
|
||||
async spawn(root) {
|
||||
const nixd = Bun.which("nixd")
|
||||
const nixd = which("nixd")
|
||||
if (!nixd) {
|
||||
log.info("nixd not found, please install nixd first")
|
||||
return
|
||||
@@ -1930,7 +1931,7 @@ export namespace LSPServer {
|
||||
extensions: [".typ", ".typc"],
|
||||
root: NearestRoot(["typst.toml"]),
|
||||
async spawn(root) {
|
||||
let bin = Bun.which("tinymist", {
|
||||
let bin = which("tinymist", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
@@ -2024,7 +2025,7 @@ export namespace LSPServer {
|
||||
extensions: [".hs", ".lhs"],
|
||||
root: NearestRoot(["stack.yaml", "cabal.project", "hie.yaml", "*.cabal"]),
|
||||
async spawn(root) {
|
||||
const bin = Bun.which("haskell-language-server-wrapper")
|
||||
const bin = which("haskell-language-server-wrapper")
|
||||
if (!bin) {
|
||||
log.info("haskell-language-server-wrapper not found, please install haskell-language-server")
|
||||
return
|
||||
@@ -2042,7 +2043,7 @@ export namespace LSPServer {
|
||||
extensions: [".jl"],
|
||||
root: NearestRoot(["Project.toml", "Manifest.toml", "*.jl"]),
|
||||
async spawn(root) {
|
||||
const julia = Bun.which("julia")
|
||||
const julia = which("julia")
|
||||
if (!julia) {
|
||||
log.info("julia not found, please install julia first (https://julialang.org/downloads/)")
|
||||
return
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createConnection } from "net"
|
||||
import { Log } from "../util/log"
|
||||
import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH } from "./oauth-provider"
|
||||
|
||||
@@ -160,21 +161,12 @@ export namespace McpOAuthCallback {
|
||||
|
||||
export async function isPortInUse(): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
Bun.connect({
|
||||
hostname: "127.0.0.1",
|
||||
port: OAUTH_CALLBACK_PORT,
|
||||
socket: {
|
||||
open(socket) {
|
||||
socket.end()
|
||||
resolve(true)
|
||||
},
|
||||
error() {
|
||||
resolve(false)
|
||||
},
|
||||
data() {},
|
||||
close() {},
|
||||
},
|
||||
}).catch(() => {
|
||||
const socket = createConnection(OAUTH_CALLBACK_PORT, "127.0.0.1")
|
||||
socket.on("connect", () => {
|
||||
socket.destroy()
|
||||
resolve(true)
|
||||
})
|
||||
socket.on("error", () => {
|
||||
resolve(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Installation } from "../installation"
|
||||
import { Auth, OAUTH_DUMMY_KEY } from "../auth"
|
||||
import os from "os"
|
||||
import { ProviderTransform } from "@/provider/transform"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
const log = Log.create({ service: "plugin.codex" })
|
||||
|
||||
@@ -361,6 +362,7 @@ export async function CodexAuthPlugin(input: PluginInput): Promise<Hooks> {
|
||||
"gpt-5.1-codex-max",
|
||||
"gpt-5.1-codex-mini",
|
||||
"gpt-5.2",
|
||||
"gpt-5.4",
|
||||
"gpt-5.2-codex",
|
||||
"gpt-5.3-codex",
|
||||
"gpt-5.1-codex",
|
||||
@@ -602,7 +604,7 @@ export async function CodexAuthPlugin(input: PluginInput): Promise<Hooks> {
|
||||
return { type: "failed" as const }
|
||||
}
|
||||
|
||||
await Bun.sleep(interval + OAUTH_POLLING_SAFETY_MARGIN_MS)
|
||||
await sleep(interval + OAUTH_POLLING_SAFETY_MARGIN_MS)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Hooks, PluginInput } from "@opencode-ai/plugin"
|
||||
import { Installation } from "@/installation"
|
||||
import { iife } from "@/util/iife"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
const CLIENT_ID = "Ov23li8tweQw6odWQebz"
|
||||
// Add a small safety buffer when polling to avoid hitting the server
|
||||
@@ -270,7 +271,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
|
||||
}
|
||||
|
||||
if (data.error === "authorization_pending") {
|
||||
await Bun.sleep(deviceData.interval * 1000 + OAUTH_POLLING_SAFETY_MARGIN_MS)
|
||||
await sleep(deviceData.interval * 1000 + OAUTH_POLLING_SAFETY_MARGIN_MS)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -286,13 +287,13 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
|
||||
newInterval = serverInterval * 1000
|
||||
}
|
||||
|
||||
await Bun.sleep(newInterval + OAUTH_POLLING_SAFETY_MARGIN_MS)
|
||||
await sleep(newInterval + OAUTH_POLLING_SAFETY_MARGIN_MS)
|
||||
continue
|
||||
}
|
||||
|
||||
if (data.error) return { type: "failed" as const }
|
||||
|
||||
await Bun.sleep(deviceData.interval * 1000 + OAUTH_POLLING_SAFETY_MARGIN_MS)
|
||||
await sleep(deviceData.interval * 1000 + OAUTH_POLLING_SAFETY_MARGIN_MS)
|
||||
continue
|
||||
}
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ import { GlobalBus } from "@/bus/global"
|
||||
import { existsSync } from "fs"
|
||||
import { git } from "../util/git"
|
||||
import { Glob } from "../util/glob"
|
||||
import { which } from "../util/which"
|
||||
|
||||
export namespace Project {
|
||||
const log = Log.create({ service: "project" })
|
||||
@@ -97,7 +98,7 @@ export namespace Project {
|
||||
if (dotgit) {
|
||||
let sandbox = path.dirname(dotgit)
|
||||
|
||||
const gitBinary = Bun.which("git")
|
||||
const gitBinary = which("git")
|
||||
|
||||
// cached id calculation
|
||||
let id = await Filesystem.readText(path.join(dotgit, "opencode"))
|
||||
|
||||
@@ -6,9 +6,10 @@ import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda"
|
||||
import { NoSuchModelError, type Provider as SDK } from "ai"
|
||||
import { Log } from "../util/log"
|
||||
import { BunProc } from "../bun"
|
||||
import { Hash } from "../util/hash"
|
||||
import { Plugin } from "../plugin"
|
||||
import { ModelsDev } from "./models"
|
||||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import { ModelsDev } from "./models"
|
||||
import { Auth } from "../auth"
|
||||
import { Env } from "../env"
|
||||
import { Instance } from "../project/instance"
|
||||
@@ -795,7 +796,7 @@ export namespace Provider {
|
||||
const modelLoaders: {
|
||||
[providerID: string]: CustomModelLoader
|
||||
} = {}
|
||||
const sdk = new Map<number, SDK>()
|
||||
const sdk = new Map<string, SDK>()
|
||||
|
||||
log.info("init")
|
||||
|
||||
@@ -1085,7 +1086,7 @@ export namespace Provider {
|
||||
...model.headers,
|
||||
}
|
||||
|
||||
const key = Bun.hash.xxHash32(JSON.stringify({ providerID: model.providerID, npm: model.api.npm, options }))
|
||||
const key = Hash.fast(JSON.stringify({ providerID: model.providerID, npm: model.api.npm, options }))
|
||||
const existing = s.sdk.get(key)
|
||||
if (existing) return existing
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Flag } from "@/flag/flag"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { which } from "@/util/which"
|
||||
import path from "path"
|
||||
import { spawn, type ChildProcess } from "child_process"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
const SIGKILL_TIMEOUT_MS = 200
|
||||
|
||||
@@ -22,13 +24,13 @@ export namespace Shell {
|
||||
|
||||
try {
|
||||
process.kill(-pid, "SIGTERM")
|
||||
await Bun.sleep(SIGKILL_TIMEOUT_MS)
|
||||
await sleep(SIGKILL_TIMEOUT_MS)
|
||||
if (!opts?.exited?.()) {
|
||||
process.kill(-pid, "SIGKILL")
|
||||
}
|
||||
} catch (_e) {
|
||||
proc.kill("SIGTERM")
|
||||
await Bun.sleep(SIGKILL_TIMEOUT_MS)
|
||||
await sleep(SIGKILL_TIMEOUT_MS)
|
||||
if (!opts?.exited?.()) {
|
||||
proc.kill("SIGKILL")
|
||||
}
|
||||
@@ -39,7 +41,7 @@ export namespace Shell {
|
||||
function fallback() {
|
||||
if (process.platform === "win32") {
|
||||
if (Flag.OPENCODE_GIT_BASH_PATH) return Flag.OPENCODE_GIT_BASH_PATH
|
||||
const git = Bun.which("git")
|
||||
const git = which("git")
|
||||
if (git) {
|
||||
// git.exe is typically at: C:\Program Files\Git\cmd\git.exe
|
||||
// bash.exe is at: C:\Program Files\Git\bin\bash.exe
|
||||
@@ -49,7 +51,7 @@ export namespace Shell {
|
||||
return process.env.COMSPEC || "cmd.exe"
|
||||
}
|
||||
if (process.platform === "darwin") return "/bin/zsh"
|
||||
const bash = Bun.which("bash")
|
||||
const bash = which("bash")
|
||||
if (bash) return bash
|
||||
return "/bin/sh"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { $ } from "bun"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Log } from "../util/log"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { Global } from "../global"
|
||||
@@ -271,13 +272,12 @@ export namespace Snapshot {
|
||||
const target = path.join(git, "info", "exclude")
|
||||
await fs.mkdir(path.join(git, "info"), { recursive: true })
|
||||
if (!file) {
|
||||
await Bun.write(target, "")
|
||||
await Filesystem.write(target, "")
|
||||
return
|
||||
}
|
||||
const text = await Bun.file(file)
|
||||
.text()
|
||||
.catch(() => "")
|
||||
await Bun.write(target, text)
|
||||
const text = await Filesystem.readText(file).catch(() => "")
|
||||
|
||||
await Filesystem.write(target, text)
|
||||
}
|
||||
|
||||
async function excludes() {
|
||||
|
||||
7
packages/opencode/src/util/hash.ts
Normal file
7
packages/opencode/src/util/hash.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createHash } from "crypto"
|
||||
|
||||
export namespace Hash {
|
||||
export function fast(input: string | Buffer): string {
|
||||
return createHash("sha1").update(input).digest("hex")
|
||||
}
|
||||
}
|
||||
10
packages/opencode/src/util/which.ts
Normal file
10
packages/opencode/src/util/which.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import whichPkg from "which"
|
||||
|
||||
export function which(cmd: string, env?: NodeJS.ProcessEnv) {
|
||||
const result = whichPkg.sync(cmd, {
|
||||
nothrow: true,
|
||||
path: env?.PATH,
|
||||
pathExt: env?.PATHEXT,
|
||||
})
|
||||
return typeof result === "string" ? result : null
|
||||
}
|
||||
@@ -474,6 +474,11 @@ export namespace Worktree {
|
||||
throw new RemoveFailedError({ message: message || "Failed to remove git worktree directory" })
|
||||
})
|
||||
|
||||
const stop = async (target: string) => {
|
||||
if (!(await exists(target))) return
|
||||
await $`git fsmonitor--daemon stop`.quiet().nothrow().cwd(target)
|
||||
}
|
||||
|
||||
const list = await $`git worktree list --porcelain`.quiet().nothrow().cwd(Instance.worktree)
|
||||
if (list.exitCode !== 0) {
|
||||
throw new RemoveFailedError({ message: errorText(list) || "Failed to read git worktrees" })
|
||||
@@ -484,11 +489,13 @@ export namespace Worktree {
|
||||
if (!entry?.path) {
|
||||
const directoryExists = await exists(directory)
|
||||
if (directoryExists) {
|
||||
await stop(directory)
|
||||
await clean(directory)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
await stop(entry.path)
|
||||
const removed = await $`git worktree remove --force ${entry.path}`.quiet().nothrow().cwd(Instance.worktree)
|
||||
if (removed.exitCode !== 0) {
|
||||
const next = await $`git worktree list --porcelain`.quiet().nothrow().cwd(Instance.worktree)
|
||||
@@ -637,7 +644,7 @@ export namespace Worktree {
|
||||
throw new ResetFailedError({ message: errorText(subClean) || "Failed to clean submodules" })
|
||||
}
|
||||
|
||||
const status = await $`git status --porcelain=v1`.quiet().nothrow().cwd(worktreePath)
|
||||
const status = await $`git -c core.fsmonitor=false status --porcelain=v1`.quiet().nothrow().cwd(worktreePath)
|
||||
if (status.exitCode !== 0) {
|
||||
throw new ResetFailedError({ message: errorText(status) || "Failed to read git status" })
|
||||
}
|
||||
|
||||
62
packages/opencode/test/file/fsmonitor.test.ts
Normal file
62
packages/opencode/test/file/fsmonitor.test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { $ } from "bun"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { File } from "../../src/file"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
const wintest = process.platform === "win32" ? test : test.skip
|
||||
|
||||
describe("file fsmonitor", () => {
|
||||
wintest("status does not start fsmonitor for readonly git checks", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
const target = path.join(tmp.path, "tracked.txt")
|
||||
|
||||
await fs.writeFile(target, "base\n")
|
||||
await $`git add tracked.txt`.cwd(tmp.path).quiet()
|
||||
await $`git commit -m init`.cwd(tmp.path).quiet()
|
||||
await $`git config core.fsmonitor true`.cwd(tmp.path).quiet()
|
||||
await $`git fsmonitor--daemon stop`.cwd(tmp.path).quiet().nothrow()
|
||||
await fs.writeFile(target, "next\n")
|
||||
await fs.writeFile(path.join(tmp.path, "new.txt"), "new\n")
|
||||
|
||||
const before = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow()
|
||||
expect(before.exitCode).not.toBe(0)
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
await File.status()
|
||||
},
|
||||
})
|
||||
|
||||
const after = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow()
|
||||
expect(after.exitCode).not.toBe(0)
|
||||
})
|
||||
|
||||
wintest("read does not start fsmonitor for git diffs", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
const target = path.join(tmp.path, "tracked.txt")
|
||||
|
||||
await fs.writeFile(target, "base\n")
|
||||
await $`git add tracked.txt`.cwd(tmp.path).quiet()
|
||||
await $`git commit -m init`.cwd(tmp.path).quiet()
|
||||
await $`git config core.fsmonitor true`.cwd(tmp.path).quiet()
|
||||
await $`git fsmonitor--daemon stop`.cwd(tmp.path).quiet().nothrow()
|
||||
await fs.writeFile(target, "next\n")
|
||||
|
||||
const before = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow()
|
||||
expect(before.exitCode).not.toBe(0)
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
await File.read("tracked.txt")
|
||||
},
|
||||
})
|
||||
|
||||
const after = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow()
|
||||
expect(after.exitCode).not.toBe(0)
|
||||
})
|
||||
})
|
||||
26
packages/opencode/test/fixture/fixture.test.ts
Normal file
26
packages/opencode/test/fixture/fixture.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { $ } from "bun"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import fs from "fs/promises"
|
||||
import { tmpdir } from "./fixture"
|
||||
|
||||
describe("tmpdir", () => {
|
||||
test("disables fsmonitor for git fixtures", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
|
||||
const value = (await $`git config core.fsmonitor`.cwd(tmp.path).quiet().text()).trim()
|
||||
expect(value).toBe("false")
|
||||
})
|
||||
|
||||
test("removes directories on dispose", async () => {
|
||||
const tmp = await tmpdir({ git: true })
|
||||
const dir = tmp.path
|
||||
|
||||
await tmp[Symbol.asyncDispose]()
|
||||
|
||||
const exists = await fs
|
||||
.stat(dir)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
expect(exists).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -9,6 +9,27 @@ function sanitizePath(p: string): string {
|
||||
return p.replace(/\0/g, "")
|
||||
}
|
||||
|
||||
function exists(dir: string) {
|
||||
return fs
|
||||
.stat(dir)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
}
|
||||
|
||||
function clean(dir: string) {
|
||||
return fs.rm(dir, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
maxRetries: 5,
|
||||
retryDelay: 100,
|
||||
})
|
||||
}
|
||||
|
||||
async function stop(dir: string) {
|
||||
if (!(await exists(dir))) return
|
||||
await $`git fsmonitor--daemon stop`.cwd(dir).quiet().nothrow()
|
||||
}
|
||||
|
||||
type TmpDirOptions<T> = {
|
||||
git?: boolean
|
||||
config?: Partial<Config.Info>
|
||||
@@ -20,6 +41,7 @@ export async function tmpdir<T>(options?: TmpDirOptions<T>) {
|
||||
await fs.mkdir(dirpath, { recursive: true })
|
||||
if (options?.git) {
|
||||
await $`git init`.cwd(dirpath).quiet()
|
||||
await $`git config core.fsmonitor false`.cwd(dirpath).quiet()
|
||||
await $`git commit --allow-empty -m "root commit ${dirpath}"`.cwd(dirpath).quiet()
|
||||
}
|
||||
if (options?.config) {
|
||||
@@ -31,12 +53,16 @@ export async function tmpdir<T>(options?: TmpDirOptions<T>) {
|
||||
}),
|
||||
)
|
||||
}
|
||||
const extra = await options?.init?.(dirpath)
|
||||
const realpath = sanitizePath(await fs.realpath(dirpath))
|
||||
const extra = await options?.init?.(realpath)
|
||||
const result = {
|
||||
[Symbol.asyncDispose]: async () => {
|
||||
await options?.dispose?.(dirpath)
|
||||
// await fs.rm(dirpath, { recursive: true, force: true })
|
||||
try {
|
||||
await options?.dispose?.(realpath)
|
||||
} finally {
|
||||
if (options?.git) await stop(realpath).catch(() => undefined)
|
||||
await clean(realpath).catch(() => undefined)
|
||||
}
|
||||
},
|
||||
path: realpath,
|
||||
extra: extra as T,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
import { afterAll } from "bun:test"
|
||||
|
||||
// Set XDG env vars FIRST, before any src/ imports
|
||||
@@ -15,7 +16,7 @@ afterAll(async () => {
|
||||
typeof error === "object" && error !== null && "code" in error && error.code === "EBUSY"
|
||||
const rm = async (left: number): Promise<void> => {
|
||||
Bun.gc(true)
|
||||
await Bun.sleep(100)
|
||||
await sleep(100)
|
||||
return fs.rm(dir, { recursive: true, force: true }).catch((error) => {
|
||||
if (!busy(error)) throw error
|
||||
if (left <= 1) throw error
|
||||
|
||||
@@ -7,6 +7,8 @@ import { Worktree } from "../../src/worktree"
|
||||
import { Filesystem } from "../../src/util/filesystem"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
const wintest = process.platform === "win32" ? test : test.skip
|
||||
|
||||
describe("Worktree.remove", () => {
|
||||
test("continues when git remove exits non-zero after detaching", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
@@ -62,4 +64,33 @@ describe("Worktree.remove", () => {
|
||||
const ref = await $`git show-ref --verify --quiet refs/heads/${branch}`.cwd(root).quiet().nothrow()
|
||||
expect(ref.exitCode).not.toBe(0)
|
||||
})
|
||||
|
||||
wintest("stops fsmonitor before removing a worktree", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
const root = tmp.path
|
||||
const name = `remove-fsmonitor-${Date.now().toString(36)}`
|
||||
const branch = `opencode/${name}`
|
||||
const dir = path.join(root, "..", name)
|
||||
|
||||
await $`git worktree add --no-checkout -b ${branch} ${dir}`.cwd(root).quiet()
|
||||
await $`git reset --hard`.cwd(dir).quiet()
|
||||
await $`git config core.fsmonitor true`.cwd(dir).quiet()
|
||||
await $`git fsmonitor--daemon stop`.cwd(dir).quiet().nothrow()
|
||||
await Bun.write(path.join(dir, "tracked.txt"), "next\n")
|
||||
await $`git diff`.cwd(dir).quiet()
|
||||
|
||||
const before = await $`git fsmonitor--daemon status`.cwd(dir).quiet().nothrow()
|
||||
expect(before.exitCode).toBe(0)
|
||||
|
||||
const ok = await Instance.provide({
|
||||
directory: root,
|
||||
fn: () => Worktree.remove({ directory: dir }),
|
||||
})
|
||||
|
||||
expect(ok).toBe(true)
|
||||
expect(await Filesystem.exists(dir)).toBe(false)
|
||||
|
||||
const ref = await $`git show-ref --verify --quiet refs/heads/${branch}`.cwd(root).quiet().nothrow()
|
||||
expect(ref.exitCode).not.toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { Pty } from "../../src/pty"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
describe("pty", () => {
|
||||
test("does not leak output when websocket objects are reused", async () => {
|
||||
@@ -43,7 +44,7 @@ describe("pty", () => {
|
||||
|
||||
// Output from a must never show up in b.
|
||||
Pty.write(a.id, "AAA\n")
|
||||
await Bun.sleep(100)
|
||||
await sleep(100)
|
||||
|
||||
expect(outB.join("")).not.toContain("AAA")
|
||||
} finally {
|
||||
@@ -88,7 +89,7 @@ describe("pty", () => {
|
||||
}
|
||||
|
||||
Pty.write(a.id, "AAA\n")
|
||||
await Bun.sleep(100)
|
||||
await sleep(100)
|
||||
|
||||
expect(outB.join("")).not.toContain("AAA")
|
||||
} finally {
|
||||
@@ -128,7 +129,7 @@ describe("pty", () => {
|
||||
ctx.connId = 2
|
||||
|
||||
Pty.write(a.id, "AAA\n")
|
||||
await Bun.sleep(100)
|
||||
await sleep(100)
|
||||
|
||||
expect(out.join("")).toContain("AAA")
|
||||
} finally {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { NamedError } from "@opencode-ai/util/error"
|
||||
import { APICallError } from "ai"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
import { SessionRetry } from "../../src/session/retry"
|
||||
import { MessageV2 } from "../../src/session/message-v2"
|
||||
|
||||
@@ -135,7 +136,7 @@ describe("session.message-v2.fromError", () => {
|
||||
new ReadableStream({
|
||||
async pull(controller) {
|
||||
controller.enqueue("Hello,")
|
||||
await Bun.sleep(10000)
|
||||
await sleep(10000)
|
||||
controller.enqueue(" World!")
|
||||
controller.close()
|
||||
},
|
||||
|
||||
82
packages/opencode/test/util/which.test.ts
Normal file
82
packages/opencode/test/util/which.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { which } from "../../src/util/which"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
async function cmd(dir: string, name: string, exec = true) {
|
||||
const ext = process.platform === "win32" ? ".cmd" : ""
|
||||
const file = path.join(dir, name + ext)
|
||||
const body = process.platform === "win32" ? "@echo off\r\n" : "#!/bin/sh\n"
|
||||
await fs.writeFile(file, body)
|
||||
if (process.platform !== "win32") {
|
||||
await fs.chmod(file, exec ? 0o755 : 0o644)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
function env(PATH: string): NodeJS.ProcessEnv {
|
||||
return {
|
||||
PATH,
|
||||
PATHEXT: process.env["PATHEXT"],
|
||||
}
|
||||
}
|
||||
|
||||
function same(a: string | null, b: string) {
|
||||
if (process.platform === "win32") {
|
||||
expect(a?.toLowerCase()).toBe(b.toLowerCase())
|
||||
return
|
||||
}
|
||||
|
||||
expect(a).toBe(b)
|
||||
}
|
||||
|
||||
describe("util.which", () => {
|
||||
test("returns null when command is missing", () => {
|
||||
expect(which("opencode-missing-command-for-test")).toBeNull()
|
||||
})
|
||||
|
||||
test("finds a command from PATH override", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const bin = path.join(tmp.path, "bin")
|
||||
await fs.mkdir(bin)
|
||||
const file = await cmd(bin, "tool")
|
||||
|
||||
same(which("tool", env(bin)), file)
|
||||
})
|
||||
|
||||
test("uses first PATH match", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const a = path.join(tmp.path, "a")
|
||||
const b = path.join(tmp.path, "b")
|
||||
await fs.mkdir(a)
|
||||
await fs.mkdir(b)
|
||||
const first = await cmd(a, "dupe")
|
||||
await cmd(b, "dupe")
|
||||
|
||||
same(which("dupe", env([a, b].join(path.delimiter))), first)
|
||||
})
|
||||
|
||||
test("returns null for non-executable file on unix", async () => {
|
||||
if (process.platform === "win32") return
|
||||
|
||||
await using tmp = await tmpdir()
|
||||
const bin = path.join(tmp.path, "bin")
|
||||
await fs.mkdir(bin)
|
||||
await cmd(bin, "noexec", false)
|
||||
|
||||
expect(which("noexec", env(bin))).toBeNull()
|
||||
})
|
||||
|
||||
test("uses PATHEXT on windows", async () => {
|
||||
if (process.platform !== "win32") return
|
||||
|
||||
await using tmp = await tmpdir()
|
||||
const bin = path.join(tmp.path, "bin")
|
||||
await fs.mkdir(bin)
|
||||
const file = path.join(bin, "pathext.CMD")
|
||||
await fs.writeFile(file, "@echo off\r\n")
|
||||
|
||||
expect(which("pathext", { PATH: bin, PATHEXT: ".CMD" })).toBe(file)
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -12,18 +12,9 @@
|
||||
".": "./src/index.ts",
|
||||
"./client": "./src/client.ts",
|
||||
"./server": "./src/server.ts",
|
||||
"./v2": {
|
||||
"types": "./dist/v2/index.d.ts",
|
||||
"default": "./src/v2/index.ts"
|
||||
},
|
||||
"./v2/client": {
|
||||
"types": "./dist/v2/client.d.ts",
|
||||
"default": "./src/v2/client.ts"
|
||||
},
|
||||
"./v2/gen/client": {
|
||||
"types": "./dist/v2/gen/client/index.d.ts",
|
||||
"default": "./src/v2/gen/client/index.ts"
|
||||
},
|
||||
"./v2": "./src/v2/index.ts",
|
||||
"./v2/client": "./src/v2/client.ts",
|
||||
"./v2/gen/client": "./src/v2/gen/client/index.ts",
|
||||
"./v2/server": "./src/v2/server.ts"
|
||||
},
|
||||
"files": [
|
||||
@@ -36,8 +27,5 @@
|
||||
"typescript": "catalog:",
|
||||
"@typescript/native-preview": "catalog:"
|
||||
},
|
||||
"dependencies": {},
|
||||
"publishConfig": {
|
||||
"directory": "dist"
|
||||
}
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -59,6 +59,7 @@ OpenCode Zen هو بوابة للذكاء الاصطناعي تتيح لك ال
|
||||
|
||||
| النموذج | معرّف النموذج | نقطة النهاية | حزمة AI SDK |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -141,6 +142,7 @@ https://opencode.ai/zen/v1/models
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -184,6 +186,19 @@ https://opencode.ai/zen/v1/models
|
||||
|
||||
---
|
||||
|
||||
### نماذج مهملة
|
||||
|
||||
| النموذج | تاريخ الإيقاف |
|
||||
| ---------------- | ------------- |
|
||||
| Qwen3 Coder 480B | 6 فبراير 2026 |
|
||||
| Kimi K2 Thinking | 6 مارس 2026 |
|
||||
| Kimi K2 | 6 مارس 2026 |
|
||||
| MiniMax M2.1 | 15 مارس 2026 |
|
||||
| GLM 4.7 | 15 مارس 2026 |
|
||||
| GLM 4.6 | 15 مارس 2026 |
|
||||
|
||||
---
|
||||
|
||||
## الخصوصية
|
||||
|
||||
تتم استضافة جميع نماذجنا في الولايات المتحدة. يلتزم مزوّدونا بسياسة عدم الاحتفاظ بالبيانات (zero-retention) ولا يستخدمون بياناتك لتدريب النماذج، مع الاستثناءات التالية:
|
||||
|
||||
@@ -55,6 +55,7 @@ Nasim modelima mozete pristupiti i preko sljedecih API endpointa.
|
||||
|
||||
| Model | Model ID | Endpoint | AI SDK Package |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -136,6 +137,7 @@ Podrzavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**.
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -178,6 +180,19 @@ Na primjer, ako postavite mjesecni limit na $20, Zen nece potrositi vise od $20
|
||||
|
||||
---
|
||||
|
||||
### Zastarjeli modeli
|
||||
|
||||
| Model | Datum ukidanja |
|
||||
| ---------------- | -------------- |
|
||||
| Qwen3 Coder 480B | 6. feb. 2026. |
|
||||
| Kimi K2 Thinking | 6. mart 2026. |
|
||||
| Kimi K2 | 6. mart 2026. |
|
||||
| MiniMax M2.1 | 15. mart 2026. |
|
||||
| GLM 4.7 | 15. mart 2026. |
|
||||
| GLM 4.6 | 15. mart 2026. |
|
||||
|
||||
---
|
||||
|
||||
## Privatnost
|
||||
|
||||
Svi nasi modeli su hostovani u SAD-u. Provajderi prate zero-retention politiku i ne koriste vase podatke za treniranje modela, uz sljedece izuzetke:
|
||||
|
||||
@@ -64,6 +64,7 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints.
|
||||
|
||||
| Model | Model ID | Endpoint | AI SDK Pakke |
|
||||
| ------------------- | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -147,6 +148,7 @@ Vi støtter en pay-as-you-go-model. Nedenfor er priserne **per 1 million tokens*
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2,00 | $12,00 | $0,20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4,00 | $18,00 | $0,40 | - |
|
||||
| Gemini 3 Flash | $0,50 | $3,00 | $0,05 | - |
|
||||
| GPT 5.4 | $2,50 | $15,00 | $0,25 | - |
|
||||
| GPT 5.3 Codex | $1,75 | $14,00 | $0,175 | - |
|
||||
| GPT 5.2 | $1,75 | $14,00 | $0,175 | - |
|
||||
| GPT 5.2 Codex | $1,75 | $14,00 | $0,175 | - |
|
||||
@@ -192,6 +194,19 @@ at opkræve dig mere end $20, hvis din saldo går under $5.
|
||||
|
||||
---
|
||||
|
||||
### Udfasede modeller
|
||||
|
||||
| Model | Udfasningsdato |
|
||||
| ---------------- | -------------- |
|
||||
| Qwen3-koder 480B | 6. feb. 2026 |
|
||||
| Kimi K2 Tenker | 6. marts 2026 |
|
||||
| Kimi K2 | 6. marts 2026 |
|
||||
| MiniMax M2.1 | 15. marts 2026 |
|
||||
| GLM 4.7 | 15. marts 2026 |
|
||||
| GLM 4.6 | 15. marts 2026 |
|
||||
|
||||
---
|
||||
|
||||
## Privatliv
|
||||
|
||||
Alle vores modeller er hostet i USA. Vores udbydere følger en nul-opbevaringspolitik og bruger ikke dine data til modeltræning, med følgende undtagelser:
|
||||
|
||||
@@ -57,6 +57,7 @@ Du kannst unsere Modelle auch ueber die folgenden API-Endpunkte aufrufen.
|
||||
|
||||
| Model | Model ID | Endpoint | AI SDK Package |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -114,12 +115,12 @@ Unten siehst du die Preise **pro 1 Mio. Tokens**.
|
||||
| --------------------------------- | ------ | ------ | ----------- | ------------ |
|
||||
| Big Pickle | Free | Free | Free | - |
|
||||
| MiniMax M2.5 Free | Free | Free | Free | - |
|
||||
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - |
|
||||
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
|
||||
| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - |
|
||||
| GLM 5 | $1.00 | $3.20 | $0.20 | - |
|
||||
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
|
||||
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
|
||||
| Kimi K2.5 | $0.60 | $3.00 | $0.08 | - |
|
||||
| Kimi K2.5 | $0.60 | $3.00 | $0.10 | - |
|
||||
| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
|
||||
| Kimi K2 | $0.40 | $2.50 | - | - |
|
||||
| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
|
||||
@@ -140,6 +141,7 @@ Unten siehst du die Preise **pro 1 Mio. Tokens**.
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -184,6 +186,19 @@ Mit aktiviertem Auto-Reload kann die Abrechnung dennoch darueber liegen, falls d
|
||||
|
||||
---
|
||||
|
||||
### Veraltete Modelle
|
||||
|
||||
| Model | Datum der Abschaltung |
|
||||
| ---------------- | --------------------- |
|
||||
| Qwen3 Coder 480B | 6. Feb. 2026 |
|
||||
| Kimi K2 Thinking | 6. Maerz 2026 |
|
||||
| Kimi K2 | 6. Maerz 2026 |
|
||||
| MiniMax M2.1 | 15. Maerz 2026 |
|
||||
| GLM 4.7 | 15. Maerz 2026 |
|
||||
| GLM 4.6 | 15. Maerz 2026 |
|
||||
|
||||
---
|
||||
|
||||
## Datenschutz
|
||||
|
||||
Alle Modelle werden in den USA gehostet.
|
||||
|
||||
@@ -62,6 +62,7 @@ También puede acceder a nuestros modelos a través de los siguientes puntos fin
|
||||
|
||||
| Modelo | Model ID | Endpoint | AI SDK package |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -145,6 +146,7 @@ Apoyamos un modelo de pago por uso. A continuación se muestran los precios **po
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0,20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0,40 | - |
|
||||
| Gemini 3 Flash | $0,50 | $3.00 | $0,05 | - |
|
||||
| GPT 5.4 | $2,50 | $15,00 | $0,25 | - |
|
||||
| GPT 5.3 Codex | $1,75 | $14.00 | $0,175 | - |
|
||||
| GPT 5.2 | $1,75 | $14.00 | $0,175 | - |
|
||||
| GPT 5.2 Codex | $1,75 | $14.00 | $0,175 | - |
|
||||
@@ -190,6 +192,19 @@ cobrarle más de $20 si su saldo es inferior a $5.
|
||||
|
||||
---
|
||||
|
||||
### Modelos obsoletos
|
||||
|
||||
| Modelo | Fecha de retiro |
|
||||
| ---------------- | ------------------- |
|
||||
| Qwen3 Coder 480B | 6 de feb. de 2026 |
|
||||
| Kimi K2 Thinking | 6 de marzo de 2026 |
|
||||
| Kimi K2 | 6 de marzo de 2026 |
|
||||
| MiniMax M2.1 | 15 de marzo de 2026 |
|
||||
| GLM 4.7 | 15 de marzo de 2026 |
|
||||
| GLM 4.6 | 15 de marzo de 2026 |
|
||||
|
||||
---
|
||||
|
||||
## Privacidad
|
||||
|
||||
Todos nuestros modelos están alojados en los EE. UU. Nuestros proveedores siguen una política de retención cero y no utilizan sus datos para la capacitación de modelos, con las siguientes excepciones:
|
||||
|
||||
@@ -55,6 +55,7 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP
|
||||
|
||||
| Modèle | ID du modèle | Point de terminaison | Package SDK IA |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -136,6 +137,7 @@ Nous soutenons un modèle de paiement à l'utilisation. Vous trouverez ci-dessou
|
||||
| Gemini 3 Pro (≤ 200K jetons) | 2,00 $ | 12,00 $ | 0,20 $ | - |
|
||||
| Gemini 3 Pro (> 200K jetons) | 4,00 $ | 18,00 $ | 0,40 $ | - |
|
||||
| Gemini 3 Flash | 0,50 $ | 3,00 $ | 0,05 $ | - |
|
||||
| GPT 5.4 | 2,50 $ | 15,00 $ | 0,25 $ | - |
|
||||
| GPT 5.3 Codex | 1,75 $ | 14,00 $ | 0,175 $ | - |
|
||||
| GPT 5.2 | 1,75 $ | 14,00 $ | 0,175 $ | - |
|
||||
| GPT 5.2 Codex | 1,75 $ | 14,00 $ | 0,175 $ | - |
|
||||
@@ -178,6 +180,19 @@ Par exemple, disons que vous définissez une limite d'utilisation mensuelle à 2
|
||||
|
||||
---
|
||||
|
||||
### Modèles obsolètes
|
||||
|
||||
| Modèle | Date de dépréciation |
|
||||
| ---------------- | -------------------- |
|
||||
| Qwen3 Coder 480B | 6 février 2026 |
|
||||
| Kimi K2 Thinking | 6 mars 2026 |
|
||||
| Kimi K2 | 6 mars 2026 |
|
||||
| MiniMax M2.1 | 15 mars 2026 |
|
||||
| GLM 4.7 | 15 mars 2026 |
|
||||
| GLM 4.6 | 15 mars 2026 |
|
||||
|
||||
---
|
||||
|
||||
## Confidentialité
|
||||
|
||||
Tous nos modèles sont hébergés aux États-Unis. Nos fournisseurs suivent une politique de rétention zéro et n'utilisent pas vos données pour la formation de modèles, avec les exceptions suivantes :
|
||||
|
||||
@@ -55,6 +55,7 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API.
|
||||
|
||||
| Modello | ID modello | Endpoint | Pacchetto AI SDK |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -136,6 +137,7 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**.
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -178,6 +180,19 @@ Per esempio, se imposti un limite mensile a $20, Zen non usera piu di $20 in un
|
||||
|
||||
---
|
||||
|
||||
### Modelli deprecati
|
||||
|
||||
| Modello | Data di deprecazione |
|
||||
| ---------------- | -------------------- |
|
||||
| Qwen3 Coder 480B | 6 feb 2026 |
|
||||
| Kimi K2 Thinking | 6 mar 2026 |
|
||||
| Kimi K2 | 6 mar 2026 |
|
||||
| MiniMax M2.1 | 15 mar 2026 |
|
||||
| GLM 4.7 | 15 mar 2026 |
|
||||
| GLM 4.6 | 15 mar 2026 |
|
||||
|
||||
---
|
||||
|
||||
## Privacy
|
||||
|
||||
Tutti i nostri modelli sono ospitati negli US. I nostri provider seguono una policy di zero-retention e non usano i tuoi dati per training dei modelli, con le seguenti eccezioni:
|
||||
|
||||
@@ -54,6 +54,7 @@ OpenCode Zen は、OpenCode の他のプロバイダーと同様に機能しま
|
||||
|
||||
| Model | Model ID | Endpoint | AI SDK Package |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -137,6 +138,7 @@ https://opencode.ai/zen/v1/models
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -179,6 +181,19 @@ https://opencode.ai/zen/v1/models
|
||||
|
||||
---
|
||||
|
||||
### 非推奨モデル
|
||||
|
||||
| Model | Deprecation date |
|
||||
| ---------------- | ---------------- |
|
||||
| Qwen3 Coder 480B | 2026年2月6日 |
|
||||
| Kimi K2 Thinking | 2026年3月6日 |
|
||||
| Kimi K2 | 2026年3月6日 |
|
||||
| MiniMax M2.1 | 2026年3月15日 |
|
||||
| GLM 4.7 | 2026年3月15日 |
|
||||
| GLM 4.6 | 2026年3月15日 |
|
||||
|
||||
---
|
||||
|
||||
## プライバシー
|
||||
|
||||
すべてのモデルは米国でホストされています。当社のプロバイダーはゼロ保持ポリシーに従い、次の例外を除いて、モデルのトレーニングにデータを使用しません。
|
||||
|
||||
@@ -55,6 +55,7 @@ OpenCode Zen은 OpenCode의 다른 제공자와 동일한 방식으로 작동합
|
||||
|
||||
| 모델 | 모델 ID | 엔드포인트 | AI SDK 패키지 |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -111,12 +112,12 @@ https://opencode.ai/zen/v1/models
|
||||
| --------------------------------- | ------ | ------ | --------- | --------- |
|
||||
| Big Pickle | Free | Free | Free | - |
|
||||
| MiniMax M2.5 Free | Free | Free | Free | - |
|
||||
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - |
|
||||
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
|
||||
| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - |
|
||||
| GLM 5 | $1.00 | $3.20 | $0.20 | - |
|
||||
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
|
||||
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
|
||||
| Kimi K2.5 | $0.60 | $3.00 | $0.08 | - |
|
||||
| Kimi K2.5 | $0.60 | $3.00 | $0.10 | - |
|
||||
| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
|
||||
| Kimi K2 | $0.40 | $2.50 | - | - |
|
||||
| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
|
||||
@@ -137,6 +138,7 @@ https://opencode.ai/zen/v1/models
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -180,6 +182,19 @@ https://opencode.ai/zen/v1/models
|
||||
|
||||
---
|
||||
|
||||
### 지원 중단 모델
|
||||
|
||||
| 모델 | 지원 중단일 |
|
||||
| ---------------- | --------------- |
|
||||
| Qwen3 Coder 480B | 2026년 2월 6일 |
|
||||
| Kimi K2 Thinking | 2026년 3월 6일 |
|
||||
| Kimi K2 | 2026년 3월 6일 |
|
||||
| MiniMax M2.1 | 2026년 3월 15일 |
|
||||
| GLM 4.7 | 2026년 3월 15일 |
|
||||
| GLM 4.6 | 2026년 3월 15일 |
|
||||
|
||||
---
|
||||
|
||||
## 개인정보 보호
|
||||
|
||||
당사의 모든 모델은 미국에서 호스팅됩니다. 당사 제공자는 데이터 무보존(zero-retention) 정책을 따르며, 아래의 예외를 제외하고는 귀하의 데이터를 모델 학습에 사용하지 않습니다.
|
||||
|
||||
@@ -64,6 +64,7 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter.
|
||||
|
||||
| Modell | Modell ID | Endepunkt | AI SDK Pakke |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -121,7 +122,7 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1 million tokens*
|
||||
| --------------------------------- | ------- | ------ | ------------- | --------------- |
|
||||
| Big Pickle | Gratis | Gratis | Gratis | - |
|
||||
| MiniMax M2.5 Free | Gratis | Gratis | Gratis | - |
|
||||
| MiniMax M2.5 | $0,30 | $1,20 | $0,06 | - |
|
||||
| MiniMax M2.5 | $0,30 | $1,20 | $0,06 | $0,375 |
|
||||
| MiniMax M2.1 | $0,30 | $1,20 | $0,10 | - |
|
||||
| GLM 5 | $1,00 | $3,20 | $0,20 | - |
|
||||
| GLM 4.7 | $0,60 | $2,20 | $0,10 | - |
|
||||
@@ -147,6 +148,7 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1 million tokens*
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2,00 | $12,00 | $0,20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4,00 | $18,00 | $0,40 | - |
|
||||
| Gemini 3 Flash | $0,50 | $3,00 | $0,05 | - |
|
||||
| GPT 5.4 | $2,50 | $15,00 | $0,25 | - |
|
||||
| GPT 5.3 Codex | $1,75 | $14,00 | $0,175 | - |
|
||||
| GPT 5.2 | $1,75 | $14,00 | $0,175 | - |
|
||||
| GPT 5.2 Codex | $1,75 | $14,00 | $0,175 | - |
|
||||
@@ -192,6 +194,19 @@ belaster deg mer enn $20 hvis saldoen din går under $5.
|
||||
|
||||
---
|
||||
|
||||
### Utfasede modeller
|
||||
|
||||
| Modell | Utfasingdato |
|
||||
| ---------------- | ------------- |
|
||||
| Qwen3 Coder 480B | 6. feb. 2026 |
|
||||
| Kimi K2 Thinking | 6. mars 2026 |
|
||||
| Kimi K2 | 6. mars 2026 |
|
||||
| MiniMax M2.1 | 15. mars 2026 |
|
||||
| GLM 4.7 | 15. mars 2026 |
|
||||
| GLM 4.6 | 15. mars 2026 |
|
||||
|
||||
---
|
||||
|
||||
## Personvern
|
||||
|
||||
Alle våre modeller er hostet i USA. Leverandørene våre følger retningslinjer om ingen datalagring og bruker ikke dataene dine til modellopplæring, med følgende unntak:
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
---
|
||||
title: Zen
|
||||
description: Wyselekcjonowana lista modeli dostarczonych przez opencode.
|
||||
description: Wyselekcjonowana lista modeli dostarczonych przez OpenCode.
|
||||
---
|
||||
|
||||
import config from "../../../../config.mjs"
|
||||
export const console = config.console
|
||||
export const email = `mailto:${config.email}`
|
||||
|
||||
OpenCode Zen to lista przetestowanych i zweryfikowanych modeli udostępniona przez zespół opencode.
|
||||
OpenCode Zen to lista przetestowanych i zweryfikowanych modeli udostępniona przez zespół OpenCode.
|
||||
|
||||
:::note
|
||||
OpenCode Zen is currently in beta.
|
||||
OpenCode Zen jest obecnie w wersji beta.
|
||||
:::
|
||||
|
||||
Zen działa jak każdy inny dostawca opencode. Logujesz się do OpenCode Zen i dostajesz
|
||||
Twój klucz API. Jest **całkowicie opcjonalny** i nie musisz go używać, aby z niego korzystać
|
||||
opencode.
|
||||
Zen działa jak każdy inny dostawca w OpenCode. Logujesz się do OpenCode Zen i otrzymujesz
|
||||
swój klucz API. Jest to **całkowicie opcjonalne** i nie musisz tego używać, aby korzystać z
|
||||
OpenCode.
|
||||
|
||||
---
|
||||
|
||||
@@ -23,23 +23,23 @@ opencode.
|
||||
|
||||
Istnieje ogromna liczba modeli, ale tylko kilka z nich
|
||||
działa dobrze jako agenci kodujący. Dodatkowo większość dostawców jest
|
||||
skonfigurowana bardzo różnie; więc otrzymujesz zupełnie inną wydajność i jakość.
|
||||
skonfigurowana bardzo różnie, więc otrzymujesz bardzo różną wydajność i jakość.
|
||||
|
||||
:::tip
|
||||
Przetestowaliśmy wybraną grupę modeli i dostawców, którzy dobrze współpracują z opencode.
|
||||
Przetestowaliśmy wybraną grupę modeli i dostawców, którzy dobrze współpracują z OpenCode.
|
||||
:::
|
||||
|
||||
Jeśli więc używasz modelu za pośrednictwem czegoś takiego jak OpenRouter, nigdy nie będzie to możliwe
|
||||
Jeśli więc używasz modelu za pośrednictwem czegoś takiego jak OpenRouter, nigdy nie możesz być
|
||||
pewien, czy otrzymujesz najlepszą wersję modelu, jaki chcesz.
|
||||
|
||||
Aby to naprawić, zrobiliśmy kilka rzeczy:
|
||||
|
||||
1. Przetestowaliśmy wybraną grupę modeli i rozmawialiśmy z ich zespołami o tym, jak to zrobić
|
||||
najlepiej je uruchom.
|
||||
1. Przetestowaliśmy wybraną grupę modeli i rozmawialiśmy z ich zespołami o tym, jak
|
||||
najlepiej je uruchamiać.
|
||||
2. Następnie współpracowaliśmy z kilkoma dostawcami, aby upewnić się, że są one obsługiwane
|
||||
correctly.
|
||||
3. Na koniec porównaliśmy kombinację modelu/dostawcy i otrzymaliśmy wynik
|
||||
z listą, którą z przyjemnością polecamy.
|
||||
poprawnie.
|
||||
3. Na koniec sprawdziliśmy wydajność kombinacji modelu/dostawcy i stworzyliśmy
|
||||
listę, którą z czystym sumieniem polecamy.
|
||||
|
||||
OpenCode Zen to brama AI, która zapewnia dostęp do tych modeli.
|
||||
|
||||
@@ -47,14 +47,14 @@ OpenCode Zen to brama AI, która zapewnia dostęp do tych modeli.
|
||||
|
||||
## Jak to działa
|
||||
|
||||
OpenCode Zen działa jak każdy inny dostawca opencode.
|
||||
OpenCode Zen działa jak każdy inny dostawca w OpenCode.
|
||||
|
||||
1. Logujesz się do **<a href={console}>OpenCode Zen</a>**, dodajesz swoje rozliczenia
|
||||
szczegóły i skopiuj klucz API.
|
||||
1. Logujesz się do **<a href={console}>OpenCode Zen</a>**, dodajesz dane rozliczeniowe
|
||||
i kopiujesz swój klucz API.
|
||||
2. Uruchamiasz polecenie `/connect` w TUI, wybierasz OpenCode Zen i wklejasz klucz API.
|
||||
3. Uruchom `/models` w TUI, aby zobaczyć listę zalecanych przez nas modeli.
|
||||
|
||||
Opłata jest pobierana za każde żądanie i możesz dodać kredyty do swojego konta.
|
||||
Opłata jest pobierana za każde żądanie i możesz dodać środki do swojego konta.
|
||||
|
||||
---
|
||||
|
||||
@@ -64,6 +64,7 @@ Dostęp do naszych modeli można również uzyskać za pośrednictwem następuj
|
||||
|
||||
| Model | Identyfikator modelu | Punkt końcowy | Pakiet SDK AI |
|
||||
| ------------------ | -------------------- | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -97,9 +98,9 @@ Dostęp do naszych modeli można również uzyskać za pośrednictwem następuj
|
||||
| Qwen3 Coder 480B | qwen3-coder | `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` |
|
||||
|
||||
[Identyfikator modelu](/docs/config/#models) w konfiguracji opencode
|
||||
używa formatu `opencode/<model-id>`. Na przykład w przypadku Kodeksu GPT 5.2 zrobiłbyś to
|
||||
użyj `opencode/gpt-5.2-codex` w swojej konfiguracji.
|
||||
[Identyfikator modelu](/docs/config/#models) w konfiguracji OpenCode
|
||||
używa formatu `opencode/<model-id>`. Na przykład w przypadku GPT 5.2 Codex użyłbyś
|
||||
`opencode/gpt-5.2-codex` w swojej konfiguracji.
|
||||
|
||||
---
|
||||
|
||||
@@ -121,12 +122,12 @@ Wspieramy 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 | - |
|
||||
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - |
|
||||
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
|
||||
| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - |
|
||||
| GLM 5 | $1.00 | $3.20 | $0.20 | - |
|
||||
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
|
||||
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
|
||||
| Kimi K2.5 | $0.60 | $3.00 | $0.08 | - |
|
||||
| Kimi K2.5 | $0.60 | $3.00 | $0.10 | - |
|
||||
| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
|
||||
| Kimi K2 | $0.40 | $2.50 | - | - |
|
||||
| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
|
||||
@@ -147,6 +148,7 @@ Wspieramy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów**.
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -158,10 +160,10 @@ Wspieramy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów**.
|
||||
| GPT 5 Codex | $1.07 | $8.50 | $0.107 | - |
|
||||
| GPT 5 Nano | Free | Free | Free | - |
|
||||
|
||||
Możesz zauważyć _Claude Haiku 3.5_ w swojej historii użytkowania. To jest [model niskokosztowy](/docs/config/#models), który służy do generowania tytułów sesji.
|
||||
Możesz zauważyć _Claude Haiku 3.5_ w swojej historii użytkowania. Jest to [tani model](/docs/config/#models), który jest używany do generowania tytułów Twoich sesji.
|
||||
|
||||
:::note
|
||||
Opłaty za karty kredytowe są przenoszone na koszt (4,4% + 0,30 USD za transakcję); nie pobieramy żadnych dodatkowych opłat.
|
||||
Opłaty za karty kredytowe są przenoszone po kosztach (4,4% + 0,30 USD za transakcję); nie pobieramy nic poza tym.
|
||||
:::
|
||||
|
||||
Darmowe modele:
|
||||
@@ -177,18 +179,31 @@ Darmowe modele:
|
||||
|
||||
Jeśli Twoje saldo spadnie poniżej 5 USD, Zen automatycznie doładuje 20 USD.
|
||||
|
||||
Możesz zmienić kwotę automatycznego doładowania. Możesz także całkowicie wyłączyć automatyczne przeładowywanie.
|
||||
Możesz zmienić kwotę automatycznego doładowania. Możesz także całkowicie wyłączyć automatyczne doładowanie.
|
||||
|
||||
---
|
||||
|
||||
### Limity miesięczne
|
||||
|
||||
Możesz także ustawić miesięczny limit wykorzystania dla całego obszaru roboczego i dla każdego z nich
|
||||
członek Twojego zespołu.
|
||||
Możesz także ustawić miesięczny limit użytkowania dla całego obszaru roboczego i dla każdego
|
||||
członka Twojego zespołu.
|
||||
|
||||
Załóżmy na przykład, że ustawiłeś miesięczny limit użytkowania na 20 USD, Zen nie będzie z niego korzystał
|
||||
ponad 20 dolarów miesięcznie. Ale jeśli masz włączone automatyczne przeładowywanie, Zen może się skończyć
|
||||
obciąży Cię kwotą wyższą niż 20 USD, jeśli saldo spadnie poniżej 5 USD.
|
||||
Na przykład, jeśli ustawisz miesięczny limit użytkowania na 20 USD, Zen nie zużyje
|
||||
więcej niż 20 dolarów w miesiącu. Ale jeśli masz włączone automatyczne doładowanie, Zen może
|
||||
obciążyć Cię kwotą wyższą niż 20 USD, jeśli saldo spadnie poniżej 5 USD.
|
||||
|
||||
---
|
||||
|
||||
### Przestarzałe modele
|
||||
|
||||
| Model | Data wycofania |
|
||||
| ---------------- | -------------- |
|
||||
| Qwen3 Coder 480B | 6 lutego 2026 |
|
||||
| Kimi K2 Thinking | 6 marca 2026 |
|
||||
| Kimi K2 | 6 marca 2026 |
|
||||
| MiniMax M2.1 | 15 marca 2026 |
|
||||
| GLM 4.7 | 15 marca 2026 |
|
||||
| GLM 4.6 | 15 marca 2026 |
|
||||
|
||||
---
|
||||
|
||||
@@ -198,22 +213,22 @@ Wszystkie nasze modele są hostowane w USA. Nasi dostawcy przestrzegają polityk
|
||||
|
||||
- Big Pickle: W okresie bezpłatnym zebrane dane mogą zostać wykorzystane do udoskonalenia modelu.
|
||||
- MiniMax M2.5 Free: W okresie bezpłatnym zebrane dane mogą zostać wykorzystane do udoskonalenia modelu.
|
||||
- Interfejsy API OpenAI: żądania są przechowywane przez 30 dni zgodnie z [Zasadami dotyczącymi danych OpenAI](https://platform.openai.com/docs/guides/your-data).
|
||||
- Interfejsy API Anthropic: żądania są przechowywane przez 30 dni zgodnie z [Zasadami dotyczącymi danych firmy Anthropic](https://docs.anthropic.com/en/docs/claude-code/data-usage).
|
||||
- API OpenAI: Żądania są przechowywane przez 30 dni zgodnie z [Zasadami dotyczącymi danych OpenAI](https://platform.openai.com/docs/guides/your-data).
|
||||
- API Anthropic: Żądania są przechowywane przez 30 dni zgodnie z [Zasadami dotyczącymi danych Anthropic](https://docs.anthropic.com/en/docs/claude-code/data-usage).
|
||||
|
||||
---
|
||||
|
||||
## Dla zespołów
|
||||
|
||||
Zen świetnie sprawdza się także w zespołach. Możesz zapraszać członków zespołu, przypisywać role, zarządzać
|
||||
Zen działa świetnie także dla zespołów. Możesz zapraszać członków zespołu, przypisywać role, dobierać
|
||||
modele, z których korzysta Twój zespół i nie tylko.
|
||||
|
||||
:::note
|
||||
Obszary robocze są obecnie bezpłatne dla zespołów w ramach wersji beta.
|
||||
:::
|
||||
|
||||
Zarządzanie obszarem roboczym jest obecnie bezpłatne dla zespołów w ramach wersji beta. Będziemy
|
||||
wkrótce udostępnimy więcej szczegółów na temat cen.
|
||||
Zarządzanie obszarem roboczym jest obecnie bezpłatne dla zespołów w ramach wersji beta.
|
||||
Wkrótce udostępnimy więcej szczegółów na temat cen.
|
||||
|
||||
---
|
||||
|
||||
@@ -221,8 +236,8 @@ wkrótce udostępnimy więcej szczegółów na temat cen.
|
||||
|
||||
Możesz zapraszać członków zespołu do swojego obszaru roboczego i przypisywać role:
|
||||
|
||||
- **Administrator**: Zarządzaj modelami, członkami, kluczami API i rozliczeniami
|
||||
- **Członek**: Zarządzaj tylko własnymi kluczami API
|
||||
- **Admin**: Zarządzanie modelami, członkami, kluczami API i rozliczeniami
|
||||
- **Członek**: Zarządzanie tylko własnymi kluczami API
|
||||
|
||||
Administratorzy mogą także ustawić miesięczne limity wydatków dla każdego członka, aby utrzymać koszty pod kontrolą.
|
||||
|
||||
@@ -233,7 +248,7 @@ Administratorzy mogą także ustawić miesięczne limity wydatków dla każdego
|
||||
Administratorzy mogą włączać i wyłączać określone modele w obszarze roboczym. Żądania skierowane do wyłączonego modelu zwrócą błąd.
|
||||
|
||||
Jest to przydatne w przypadkach, gdy chcesz wyłączyć korzystanie z modelu, który
|
||||
collects data.
|
||||
zbiera dane.
|
||||
|
||||
---
|
||||
|
||||
@@ -253,6 +268,6 @@ i chcesz go używać zamiast tego, który zapewnia Zen.
|
||||
Stworzyliśmy OpenCode Zen, aby:
|
||||
|
||||
1. **Testować** (Benchmark) najlepsze modele/dostawców dla agentów kodujących.
|
||||
2. Miej dostęp do opcji **najwyższej jakości**, a nie obniżaj wydajności ani nie kieruj się do tańszych dostawców.
|
||||
3. Przekaż wszelkie **obniżki cen**, sprzedając po kosztach; więc jedyną marżą jest pokrycie naszych opłat manipulacyjnych.
|
||||
4. Nie **nie blokuj**, umożliwiając używanie go z dowolnym innym agentem kodującym. I zawsze pozwalaj na korzystanie z opencode dowolnego innego dostawcy.
|
||||
2. Mieć dostęp do opcji **najwyższej jakości**, a nie obniżać wydajności ani nie kierować do tańszych dostawców.
|
||||
3. Przekazywać wszelkie **obniżki cen**, sprzedając po kosztach; więc jedyną marżą jest pokrycie naszych opłat manipulacyjnych.
|
||||
4. Nie **mieć blokady** (no lock-in), umożliwiając używanie go z dowolnym innym agentem kodującym. I zawsze pozwalać na korzystanie z dowolnego innego dostawcy w OpenCode.
|
||||
|
||||
@@ -55,6 +55,7 @@ Você também pode acessar nossos modelos através dos seguintes endpoints da AP
|
||||
|
||||
| Modelo | ID do Modelo | Endpoint | Pacote AI SDK |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -136,6 +137,7 @@ Nós suportamos um modelo de pagamento conforme o uso. Abaixo estão os preços
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -178,6 +180,19 @@ Por exemplo, digamos que você defina um limite de uso mensal de $20, o Zen não
|
||||
|
||||
---
|
||||
|
||||
### Modelos obsoletos
|
||||
|
||||
| Modelo | Data de descontinuação |
|
||||
| ---------------- | ---------------------- |
|
||||
| Qwen3 Coder 480B | 6 de fev. de 2026 |
|
||||
| Kimi K2 Thinking | 6 de mar. de 2026 |
|
||||
| Kimi K2 | 6 de mar. de 2026 |
|
||||
| MiniMax M2.1 | 15 de mar. de 2026 |
|
||||
| GLM 4.7 | 15 de mar. de 2026 |
|
||||
| GLM 4.6 | 15 de mar. de 2026 |
|
||||
|
||||
---
|
||||
|
||||
## Privacidade
|
||||
|
||||
Todos os nossos modelos estão hospedados nos EUA. Nossos provedores seguem uma política de zero retenção e não usam seus dados para treinamento de modelos, com as seguintes exceções:
|
||||
|
||||
@@ -63,6 +63,7 @@ OpenCode Zen работает так же, как и любой другой п
|
||||
|
||||
| Модель | Идентификатор модели | Конечная точка | Пакет AI SDK |
|
||||
| ------------------ | -------------------- | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -146,6 +147,7 @@ https://opencode.ai/zen/v1/models
|
||||
| Gemini 3 Pro (≤ 200 тыс. токенов) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200 тыс. токенов) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -191,6 +193,19 @@ https://opencode.ai/zen/v1/models
|
||||
|
||||
---
|
||||
|
||||
### Устаревшие модели
|
||||
|
||||
| Модель | Дата отключения |
|
||||
| ---------------- | ---------------- |
|
||||
| Qwen3 Coder 480B | 6 февр. 2026 г. |
|
||||
| Kimi K2 Thinking | 6 марта 2026 г. |
|
||||
| Kimi K2 | 6 марта 2026 г. |
|
||||
| MiniMax M2.1 | 15 марта 2026 г. |
|
||||
| GLM 4.7 | 15 марта 2026 г. |
|
||||
| GLM 4.6 | 15 марта 2026 г. |
|
||||
|
||||
---
|
||||
|
||||
## Конфиденциальность
|
||||
|
||||
Все наши модели размещены в США. Наши поставщики придерживаются политики нулевого хранения и не используют ваши данные для обучения моделей, за следующими исключениями:
|
||||
|
||||
@@ -64,6 +64,7 @@ OpenCode Zen ทำงานเหมือนกับผู้ให้บร
|
||||
|
||||
| Model | Model ID | Endpoint | แพ็คเกจ AI SDK |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -121,12 +122,12 @@ https://opencode.ai/zen/v1/models
|
||||
| --------------------------------- | ---------- | -------- | ------- | ---------- |
|
||||
| Big Pickle | ฟรี | ฟรี | ฟรี | - |
|
||||
| MiniMax M2.5 Free | ฟรี | ฟรี | ฟรี | - |
|
||||
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | - |
|
||||
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
|
||||
| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - |
|
||||
| GLM 5 | $1.00 | $3.20 | $0.20 | - |
|
||||
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
|
||||
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
|
||||
| Kimi K2.5 | $0.60 | $3.00 | $0.08 | - |
|
||||
| Kimi K2.5 | $0.60 | $3.00 | $0.10 | - |
|
||||
| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
|
||||
| Kimi K2 | $0.40 | $2.50 | - | - |
|
||||
| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
|
||||
@@ -147,6 +148,7 @@ https://opencode.ai/zen/v1/models
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -192,11 +194,24 @@ https://opencode.ai/zen/v1/models
|
||||
|
||||
---
|
||||
|
||||
### โมเดลที่เลิกใช้แล้ว
|
||||
|
||||
| Model | วันที่เลิกใช้ |
|
||||
| ---------------- | ------------- |
|
||||
| Qwen3 Coder 480B | 6 ก.พ. 2026 |
|
||||
| Kimi K2 Thinking | 6 มี.ค. 2026 |
|
||||
| Kimi K2 | 6 มี.ค. 2026 |
|
||||
| MiniMax M2.1 | 15 มี.ค. 2026 |
|
||||
| GLM 4.7 | 15 มี.ค. 2026 |
|
||||
| GLM 4.6 | 15 มี.ค. 2026 |
|
||||
|
||||
---
|
||||
|
||||
## ความเป็นส่วนตัว
|
||||
|
||||
โมเดลทั้งหมดของเราโฮสต์ในสหรัฐอเมริกา ผู้ให้บริการของเราปฏิบัติตามนโยบายการเก็บรักษาเป็นศูนย์ และไม่ใช้ข้อมูลของคุณสำหรับการฝึกโมเดล โดยมีข้อยกเว้นต่อไปนี้:
|
||||
|
||||
- Big Pickle: ในช่วงระยะเวลาว่าง ข้อมูลที่รวบรวมอาจนำไปใช้ในการปรับปรุงโมเดลได้
|
||||
- Big Pickle: ในช่วงระยะเวลาฟรี ข้อมูลที่รวบรวมอาจนำไปใช้ในการปรับปรุงโมเดลได้
|
||||
- MiniMax M2.5 Free: ในช่วงระยะเวลาฟรี ข้อมูลที่รวบรวมอาจนำไปใช้ในการปรับปรุงโมเดล
|
||||
- OpenAI API: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [นโยบายข้อมูลของ OpenAI](https://platform.openai.com/docs/guides/your-data)
|
||||
- Anthropic API: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [นโยบายข้อมูลของ Anthropic](https://docs.anthropic.com/en/docs/claude-code/data-usage)
|
||||
|
||||
@@ -55,6 +55,7 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin
|
||||
|
||||
| Model | Model ID | Endpoint | AI SDK Package |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -136,6 +137,7 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1 milyon token başına*
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -178,6 +180,19 @@ Ayrıca tüm çalışma alanı ve ekibinizin her üyesi için aylık kullanım l
|
||||
|
||||
---
|
||||
|
||||
### Kullanımdan kaldırılan modeller
|
||||
|
||||
| Model | Kullanımdan kaldırılma tarihi |
|
||||
| ---------------- | ----------------------------- |
|
||||
| Qwen3 Coder 480B | 6 Şub 2026 |
|
||||
| Kimi K2 Thinking | 6 Mar 2026 |
|
||||
| Kimi K2 | 6 Mar 2026 |
|
||||
| MiniMax M2.1 | 15 Mar 2026 |
|
||||
| GLM 4.7 | 15 Mar 2026 |
|
||||
| GLM 4.6 | 15 Mar 2026 |
|
||||
|
||||
---
|
||||
|
||||
## Gizlilik
|
||||
|
||||
Tüm modellerimiz ABD'de barındırılmaktadır. Sağlayıcılarımız sıfır saklama politikasını izler ve aşağıdaki istisnalar dışında verilerinizi model eğitimi için kullanmaz:
|
||||
|
||||
@@ -62,44 +62,47 @@ You are charged per request and you can add credits to your account.
|
||||
|
||||
You can also access our models through the following API endpoints.
|
||||
|
||||
| Model | Model ID | Endpoint | AI SDK Package |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
| Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` |
|
||||
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
|
||||
| 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.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| Qwen3 Coder 480B | qwen3-coder | `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` |
|
||||
| Model | Model ID | Endpoint | AI SDK Package |
|
||||
| ------------------- | ------------------- | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 Pro | gpt-5.4-pro | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex Spark | gpt-5.3-codex-spark | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| Claude Opus 4.6 | claude-opus-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Sonnet 4.6 | claude-sonnet-4-6 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
| Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` |
|
||||
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
|
||||
| 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.1 | minimax-m2.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
|
||||
| Qwen3 Coder 480B | qwen3-coder | `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` |
|
||||
|
||||
The [model id](/docs/config/#models) in your OpenCode config
|
||||
uses the format `opencode/<model-id>`. For example, for GPT 5.2 Codex, you would
|
||||
use `opencode/gpt-5.2-codex` in your config.
|
||||
uses the format `opencode/<model-id>`. For example, for GPT 5.3 Codex, you would
|
||||
use `opencode/gpt-5.3-codex` in your config.
|
||||
|
||||
---
|
||||
|
||||
@@ -117,46 +120,49 @@ https://opencode.ai/zen/v1/models
|
||||
|
||||
We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
|
||||
|
||||
| Model | Input | Output | Cached Read | Cached Write |
|
||||
| --------------------------------- | ------ | ------ | ----------- | ------------ |
|
||||
| Big Pickle | Free | Free | Free | - |
|
||||
| MiniMax M2.5 Free | Free | Free | Free | - |
|
||||
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
|
||||
| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - |
|
||||
| GLM 5 | $1.00 | $3.20 | $0.20 | - |
|
||||
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
|
||||
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
|
||||
| Kimi K2.5 | $0.60 | $3.00 | $0.10 | - |
|
||||
| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
|
||||
| Kimi K2 | $0.40 | $2.50 | - | - |
|
||||
| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
|
||||
| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 |
|
||||
| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 |
|
||||
| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 |
|
||||
| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 |
|
||||
| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
|
||||
| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
|
||||
| Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.1 | $1.07 | $8.50 | $0.107 | - |
|
||||
| GPT 5.1 Codex | $1.07 | $8.50 | $0.107 | - |
|
||||
| GPT 5.1 Codex Max | $1.25 | $10.00 | $0.125 | - |
|
||||
| GPT 5.1 Codex Mini | $0.25 | $2.00 | $0.025 | - |
|
||||
| GPT 5 | $1.07 | $8.50 | $0.107 | - |
|
||||
| GPT 5 Codex | $1.07 | $8.50 | $0.107 | - |
|
||||
| GPT 5 Nano | Free | Free | Free | - |
|
||||
| Model | Input | Output | Cached Read | Cached Write |
|
||||
| --------------------------------- | ------ | ------- | ----------- | ------------ |
|
||||
| Big Pickle | Free | Free | Free | - |
|
||||
| MiniMax M2.5 Free | Free | Free | Free | - |
|
||||
| MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 |
|
||||
| MiniMax M2.1 | $0.30 | $1.20 | $0.10 | - |
|
||||
| GLM 5 | $1.00 | $3.20 | $0.20 | - |
|
||||
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
|
||||
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
|
||||
| Kimi K2.5 | $0.60 | $3.00 | $0.10 | - |
|
||||
| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
|
||||
| Kimi K2 | $0.40 | $2.50 | - | - |
|
||||
| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
|
||||
| Claude Opus 4.6 (≤ 200K tokens) | $5.00 | $25.00 | $0.50 | $6.25 |
|
||||
| Claude Opus 4.6 (> 200K tokens) | $10.00 | $37.50 | $1.00 | $12.50 |
|
||||
| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 |
|
||||
| Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 |
|
||||
| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
|
||||
| Claude Sonnet 4.6 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Sonnet 4.5 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Sonnet 4 (≤ 200K tokens) | $3.00 | $15.00 | $0.30 | $3.75 |
|
||||
| Claude Sonnet 4 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex Spark | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.1 | $1.07 | $8.50 | $0.107 | - |
|
||||
| GPT 5.1 Codex | $1.07 | $8.50 | $0.107 | - |
|
||||
| GPT 5.1 Codex Max | $1.25 | $10.00 | $0.125 | - |
|
||||
| GPT 5.1 Codex Mini | $0.25 | $2.00 | $0.025 | - |
|
||||
| GPT 5 | $1.07 | $8.50 | $0.107 | - |
|
||||
| GPT 5 Codex | $1.07 | $8.50 | $0.107 | - |
|
||||
| GPT 5 Nano | Free | Free | Free | - |
|
||||
|
||||
You might notice _Claude Haiku 3.5_ in your usage history. This is a [low cost model](/docs/config/#models) that's used to generate the titles of your sessions.
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。
|
||||
|
||||
| 模型 | 模型 ID | 端点 | AI SDK 包 |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -136,6 +137,7 @@ https://opencode.ai/zen/v1/models
|
||||
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -178,6 +180,19 @@ https://opencode.ai/zen/v1/models
|
||||
|
||||
---
|
||||
|
||||
### 已弃用模型
|
||||
|
||||
| 模型 | 弃用日期 |
|
||||
| ---------------- | ------------------ |
|
||||
| Qwen3 Coder 480B | 2026 年 2 月 6 日 |
|
||||
| Kimi K2 Thinking | 2026 年 3 月 6 日 |
|
||||
| Kimi K2 | 2026 年 3 月 6 日 |
|
||||
| MiniMax M2.1 | 2026 年 3 月 15 日 |
|
||||
| GLM 4.7 | 2026 年 3 月 15 日 |
|
||||
| GLM 4.6 | 2026 年 3 月 15 日 |
|
||||
|
||||
---
|
||||
|
||||
## 隐私
|
||||
|
||||
我们所有的模型都托管在美国。我们的提供商遵循零保留政策,不会将你的数据用于模型训练,但以下情况除外:
|
||||
|
||||
@@ -55,6 +55,7 @@ OpenCode Zen 的工作方式與 OpenCode 中的任何其他供應商相同。
|
||||
|
||||
| 模型 | 模型 ID | 端點 | AI SDK 套件 |
|
||||
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
|
||||
| GPT 5.4 | gpt-5.4 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.3 Codex | gpt-5.3-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
| GPT 5.2 Codex | gpt-5.2-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
|
||||
@@ -136,6 +137,7 @@ https://opencode.ai/zen/v1/models
|
||||
| Gemini 3 Pro (≤ 200K Token) | $2.00 | $12.00 | $0.20 | - |
|
||||
| Gemini 3 Pro (> 200K Token) | $4.00 | $18.00 | $0.40 | - |
|
||||
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
|
||||
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
|
||||
| GPT 5.3 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 | $1.75 | $14.00 | $0.175 | - |
|
||||
| GPT 5.2 Codex | $1.75 | $14.00 | $0.175 | - |
|
||||
@@ -178,6 +180,19 @@ https://opencode.ai/zen/v1/models
|
||||
|
||||
---
|
||||
|
||||
### 已棄用的模型
|
||||
|
||||
| 模型 | 棄用日期 |
|
||||
| ---------------- | ------------------ |
|
||||
| Qwen3 Coder 480B | 2026 年 2 月 6 日 |
|
||||
| Kimi K2 Thinking | 2026 年 3 月 6 日 |
|
||||
| Kimi K2 | 2026 年 3 月 6 日 |
|
||||
| MiniMax M2.1 | 2026 年 3 月 15 日 |
|
||||
| GLM 4.7 | 2026 年 3 月 15 日 |
|
||||
| GLM 4.6 | 2026 年 3 月 15 日 |
|
||||
|
||||
---
|
||||
|
||||
## 隱私
|
||||
|
||||
我們所有的模型都託管在美國。我們的供應商遵循零保留政策,不會將你的資料用於模型訓練,但以下情況除外:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "1.2.17",
|
||||
"version": "1.2.20",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user