diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td index 1172850777..8bc1d8e2b8 100644 --- a/.github/VOUCHED.td +++ b/.github/VOUCHED.td @@ -27,6 +27,7 @@ r44vc0rp rekram1-node -ricardo-m-l -robinmordasiewicz +shantur simonklee -spider-yamet clawdbot/llm psychosis, spam pinging the team thdxr diff --git a/bun.lock b/bun.lock index 3cb0b868d7..aeab042cf3 100644 --- a/bun.lock +++ b/bun.lock @@ -27,12 +27,12 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", + "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", - "@opencode-ai/util": "workspace:*", "@shikijs/transformers": "3.9.2", "@solid-primitives/active-element": "2.1.3", "@solid-primitives/audio": "1.4.2", @@ -81,7 +81,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@cloudflare/vite-plugin": "1.15.2", "@ibm/plex": "6.4.1", @@ -115,7 +115,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -142,7 +142,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -166,7 +166,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -190,7 +190,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -223,7 +223,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "effect": "catalog:", "electron-context-menu": "4.1.2", @@ -266,10 +266,10 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { + "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", - "@opencode-ai/util": "workspace:*", "@pierre/diffs": "catalog:", "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", @@ -295,7 +295,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -311,7 +311,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.4.4", + "version": "1.4.6", "bin": { "opencode": "./bin/opencode", }, @@ -358,8 +358,9 @@ "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/server": "workspace:*", - "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "2.5.1", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", @@ -424,6 +425,7 @@ "@effect/language-service": "0.84.2", "@octokit/webhooks-types": "7.6.1", "@opencode-ai/script": "workspace:*", + "@opencode-ai/shared": "workspace:*", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", @@ -455,7 +457,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@opencode-ai/sdk": "workspace:*", "effect": "catalog:", @@ -490,7 +492,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "cross-spawn": "catalog:", }, @@ -505,17 +507,39 @@ }, "packages/server": { "name": "@opencode-ai/server", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "effect": "catalog:", }, "devDependencies": { + "@typescript/native-preview": "catalog:", "typescript": "catalog:", }, }, + "packages/shared": { + "name": "@opencode-ai/shared", + "version": "1.4.6", + "bin": { + "opencode": "./bin/opencode", + }, + "dependencies": { + "@effect/platform-node": "catalog:", + "@npmcli/arborist": "catalog:", + "effect": "catalog:", + "mime-types": "3.0.2", + "minimatch": "10.2.5", + "semver": "catalog:", + "xdg-basedir": "5.1.0", + "zod": "catalog:", + }, + "devDependencies": { + "@types/bun": "catalog:", + "@types/semver": "catalog:", + }, + }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -550,11 +574,11 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", - "@opencode-ai/util": "workspace:*", + "@opencode-ai/shared": "workspace:*", "@pierre/diffs": "catalog:", "@shikijs/transformers": "3.9.2", "@solid-primitives/bounds": "0.1.3", @@ -597,20 +621,9 @@ "vite-plugin-solid": "catalog:", }, }, - "packages/util": { - "name": "@opencode-ai/util", - "version": "1.4.4", - "dependencies": { - "zod": "catalog:", - }, - "devDependencies": { - "@types/bun": "catalog:", - "typescript": "catalog:", - }, - }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -666,6 +679,7 @@ "@hono/zod-validator": "0.4.2", "@kobalte/core": "0.13.11", "@lydell/node-pty": "1.2.0-beta.10", + "@npmcli/arborist": "9.4.0", "@octokit/rest": "22.0.0", "@openauthjs/openauth": "0.0.0-20250322224806", "@pierre/diffs": "1.1.0-beta.18", @@ -698,6 +712,7 @@ "marked-shiki": "1.2.1", "remeda": "2.26.0", "remend": "1.3.0", + "semver": "7.7.4", "shiki": "3.20.0", "solid-js": "1.9.10", "solid-list": "0.3.0", @@ -1554,14 +1569,14 @@ "@opencode-ai/server": ["@opencode-ai/server@workspace:packages/server"], + "@opencode-ai/shared": ["@opencode-ai/shared@workspace:packages/shared"], + "@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"], "@opencode-ai/storybook": ["@opencode-ai/storybook@workspace:packages/storybook"], "@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"], - "@opencode-ai/util": ["@opencode-ai/util@workspace:packages/util"], - "@opencode-ai/web": ["@opencode-ai/web@workspace:packages/web"], "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.5.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-r1fJL1Cb3gQDa2MpWH/sfx1BsEW0uzlRriJM6eihaKqbtKDmZoBisF32VcVaQYassighX7NGCkF68EsrZA43uQ=="], @@ -2780,7 +2795,7 @@ "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - "common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], + "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], "compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="], @@ -3880,7 +3895,7 @@ "miniflare": ["miniflare@4.20251118.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251118.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-uLSAE/DvOm392fiaig4LOaatxLjM7xzIniFRG5Y3yF9IduOYLLK/pkCPQNCgKQH3ou0YJRHnTN+09LPfqYNTQQ=="], - "minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], @@ -3938,7 +3953,7 @@ "native-duplexpair": ["native-duplexpair@1.0.0", "", {}, "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA=="], - "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], @@ -5440,12 +5455,6 @@ "@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], - "@npmcli/arborist/common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], - - "@npmcli/arborist/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - - "@npmcli/map-workspaces/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "@npmcli/query/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], "@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], @@ -5614,8 +5623,6 @@ "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], - "@tufjs/models/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], "@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], @@ -5628,6 +5635,8 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + "ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.95", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ=="], "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="], @@ -5650,8 +5659,6 @@ "app-builder-lib/hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], - "app-builder-lib/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "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=="], @@ -5660,6 +5667,8 @@ "astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="], + "astro/common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], + "astro/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="], "astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="], @@ -5784,8 +5793,6 @@ "gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], @@ -5802,8 +5809,6 @@ "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], - "ignore-walk/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "js-beautify/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=="], "js-beautify/nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], @@ -5818,8 +5823,6 @@ "log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="], @@ -5858,6 +5861,8 @@ "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "opencode/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="], + "opencode-gitlab-auth/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "opencode-poe-auth/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], @@ -6668,8 +6673,6 @@ "type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "vite-plugin-icons-spritesheet/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - "vitest/@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], @@ -6806,8 +6809,6 @@ "@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="], - "@electron/rebuild/node-gyp/make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "@electron/rebuild/node-gyp/make-fetch-happen/ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], "@electron/rebuild/node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], @@ -6872,8 +6873,6 @@ "@jsx-email/cli/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], - "@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], "@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], @@ -7072,8 +7071,6 @@ "js-beautify/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], "pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], diff --git a/nix/hashes.json b/nix/hashes.json index 0558d6b3d8..a12e6f7e5e 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-gdS7MkWGeVO0qLs0HKD156YE0uCk5vWeYjKu4JR1Apw=", - "aarch64-linux": "sha256-tF4pyVqzbrvdkRG23Fot37FCg8guRZkcU738fHPr/OQ=", - "aarch64-darwin": "sha256-FugTWzGMb2ktAbNwQvWRM3GWOb5RTR++8EocDDrQMLc=", - "x86_64-darwin": "sha256-jpe6EiwKr+CS00cn0eHwcDluO4LvO3t/5l/LcFBBKP0=" + "x86_64-linux": "sha256-PvIx2g1J5QIUIzkz2ABaAM4K/k/+xlBPDUExoOJNNuo=", + "aarch64-linux": "sha256-YTAL+P13L5hgNJdDSiBED/UNa5zdTntnUUYDYL+Jdzo=", + "aarch64-darwin": "sha256-y2VCJifYAp+H0lpDcJ0QfKNMG00Q/usFElaUIpdc8Vs=", + "x86_64-darwin": "sha256-yz8edIlqLp06Y95ad8YjKz5azP7YATPle4TcDx6lM+U=" } } diff --git a/package.json b/package.json index 282506206b..abe1b5d362 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "catalog": { "@effect/opentelemetry": "4.0.0-beta.48", "@effect/platform-node": "4.0.0-beta.48", + "@npmcli/arborist": "9.4.0", "@types/bun": "1.3.11", "@types/cross-spawn": "6.0.6", "@octokit/rest": "22.0.0", @@ -59,6 +60,7 @@ "marked-shiki": "1.2.1", "remend": "1.3.0", "@playwright/test": "1.59.1", + "semver": "7.7.4", "typescript": "5.8.2", "@typescript/native-preview": "7.0.0-dev.20251207.1", "zod": "4.1.8", diff --git a/packages/app/package.json b/packages/app/package.json index 639d6e76f0..483c71dc50 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.4.4", + "version": "1.4.6", "description": "", "type": "module", "exports": { @@ -42,7 +42,7 @@ "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/ui": "workspace:*", - "@opencode-ai/util": "workspace:*", + "@opencode-ai/shared": "workspace:*", "@shikijs/transformers": "3.9.2", "@solid-primitives/active-element": "2.1.3", "@solid-primitives/audio": "1.4.2", diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx index eb962f47eb..ea5d70065a 100644 --- a/packages/app/src/components/dialog-edit-project.tsx +++ b/packages/app/src/components/dialog-edit-project.tsx @@ -9,7 +9,7 @@ import { createStore } from "solid-js/store" import { useGlobalSDK } from "@/context/global-sdk" import { useGlobalSync } from "@/context/global-sync" import { type LocalProject, getAvatarColors } from "@/context/layout" -import { getFilename } from "@opencode-ai/util/path" +import { getFilename } from "@opencode-ai/shared/util/path" import { Avatar } from "@opencode-ai/ui/avatar" import { useLanguage } from "@/context/language" diff --git a/packages/app/src/components/dialog-fork.tsx b/packages/app/src/components/dialog-fork.tsx index 9e1b896fa8..710618c301 100644 --- a/packages/app/src/components/dialog-fork.tsx +++ b/packages/app/src/components/dialog-fork.tsx @@ -9,7 +9,7 @@ import { List } from "@opencode-ai/ui/list" import { showToast } from "@opencode-ai/ui/toast" import { extractPromptFromParts } from "@/utils/prompt" import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client" -import { base64Encode } from "@opencode-ai/util/encode" +import { base64Encode } from "@opencode-ai/shared/util/encode" import { useLanguage } from "@/context/language" interface ForkableMessage { diff --git a/packages/app/src/components/dialog-select-directory.tsx b/packages/app/src/components/dialog-select-directory.tsx index 91e23f8ffa..903cb1915d 100644 --- a/packages/app/src/components/dialog-select-directory.tsx +++ b/packages/app/src/components/dialog-select-directory.tsx @@ -3,7 +3,7 @@ import { Dialog } from "@opencode-ai/ui/dialog" import { FileIcon } from "@opencode-ai/ui/file-icon" import { List } from "@opencode-ai/ui/list" import type { ListRef } from "@opencode-ai/ui/list" -import { getDirectory, getFilename } from "@opencode-ai/util/path" +import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" import fuzzysort from "fuzzysort" import { createMemo, createResource, createSignal } from "solid-js" import { useGlobalSDK } from "@/context/global-sdk" diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index e21be77fb9..a0347a0399 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -4,8 +4,8 @@ import { FileIcon } from "@opencode-ai/ui/file-icon" import { Icon } from "@opencode-ai/ui/icon" import { Keybind } from "@opencode-ai/ui/keybind" import { List } from "@opencode-ai/ui/list" -import { base64Encode } from "@opencode-ai/util/encode" -import { getDirectory, getFilename } from "@opencode-ai/util/path" +import { base64Encode } from "@opencode-ai/shared/util/encode" +import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" import { useNavigate } from "@solidjs/router" import { createMemo, createSignal, Match, onCleanup, Show, Switch } from "solid-js" import { formatKeybind, useCommand, type CommandOption } from "@/context/command" diff --git a/packages/app/src/components/prompt-input/build-request-parts.ts b/packages/app/src/components/prompt-input/build-request-parts.ts index a1076e60ca..c268af35ee 100644 --- a/packages/app/src/components/prompt-input/build-request-parts.ts +++ b/packages/app/src/components/prompt-input/build-request-parts.ts @@ -1,4 +1,4 @@ -import { getFilename } from "@opencode-ai/util/path" +import { getFilename } from "@opencode-ai/shared/util/path" import { type AgentPartInput, type FilePartInput, type Part, type TextPartInput } from "@opencode-ai/sdk/v2/client" import type { FileSelection } from "@/context/file" import { encodeFilePath } from "@/context/file/path" diff --git a/packages/app/src/components/prompt-input/context-items.tsx b/packages/app/src/components/prompt-input/context-items.tsx index b138fe3ef6..9f20f1c04b 100644 --- a/packages/app/src/components/prompt-input/context-items.tsx +++ b/packages/app/src/components/prompt-input/context-items.tsx @@ -2,7 +2,7 @@ import { Component, For, Show } from "solid-js" import { FileIcon } from "@opencode-ai/ui/file-icon" import { IconButton } from "@opencode-ai/ui/icon-button" import { Tooltip } from "@opencode-ai/ui/tooltip" -import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/util/path" +import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/shared/util/path" import type { ContextItem } from "@/context/prompt" type PromptContextItem = ContextItem & { key: string } diff --git a/packages/app/src/components/prompt-input/slash-popover.tsx b/packages/app/src/components/prompt-input/slash-popover.tsx index 65eb01c797..0c8c959234 100644 --- a/packages/app/src/components/prompt-input/slash-popover.tsx +++ b/packages/app/src/components/prompt-input/slash-popover.tsx @@ -1,7 +1,7 @@ import { Component, For, Match, Show, Switch } from "solid-js" import { FileIcon } from "@opencode-ai/ui/file-icon" import { Icon } from "@opencode-ai/ui/icon" -import { getDirectory, getFilename } from "@opencode-ai/util/path" +import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" export type AtOption = | { type: "agent"; name: string; display: string } diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts index 03bece2e31..cf99497232 100644 --- a/packages/app/src/components/prompt-input/submit.test.ts +++ b/packages/app/src/components/prompt-input/submit.test.ts @@ -74,7 +74,7 @@ beforeAll(async () => { showToast: () => 0, })) - mock.module("@opencode-ai/util/encode", () => ({ + mock.module("@opencode-ai/shared/util/encode", () => ({ base64Encode: (value: string) => value, })) diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index ad759d08e4..27e8980431 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -1,9 +1,9 @@ import type { Message, Session } from "@opencode-ai/sdk/v2/client" import { showToast } from "@opencode-ai/ui/toast" -import { base64Encode } from "@opencode-ai/util/encode" -import { Binary } from "@opencode-ai/util/binary" +import { base64Encode } from "@opencode-ai/shared/util/encode" +import { Binary } from "@opencode-ai/shared/util/binary" import { useNavigate, useParams } from "@solidjs/router" -import type { Accessor } from "solid-js" +import { batch, type Accessor } from "solid-js" import type { FileSelection } from "@/context/file" import { useGlobalSync } from "@/context/global-sync" import { useLanguage } from "@/context/language" @@ -138,13 +138,17 @@ export async function sendFollowupDraft(input: FollowupSendInput) { messageID, }) - setBusy() - add() + batch(() => { + setBusy() + add() + }) try { if (!(await wait())) { - setIdle() - remove() + batch(() => { + setIdle() + remove() + }) return false } @@ -158,8 +162,10 @@ export async function sendFollowupDraft(input: FollowupSendInput) { }) return true } catch (err) { - setIdle() - remove() + batch(() => { + setIdle() + remove() + }) throw err } } diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx index 4e7dc8e783..abf4c93346 100644 --- a/packages/app/src/components/session/session-context-tab.tsx +++ b/packages/app/src/components/session/session-context-tab.tsx @@ -1,8 +1,8 @@ import { createMemo, createEffect, on, onCleanup, For, Show } from "solid-js" import type { JSX } from "solid-js" import { useSync } from "@/context/sync" -import { checksum } from "@opencode-ai/util/encode" -import { findLast } from "@opencode-ai/util/array" +import { checksum } from "@opencode-ai/shared/util/encode" +import { findLast } from "@opencode-ai/shared/util/array" import { same } from "@/utils/same" import { Icon } from "@opencode-ai/ui/icon" import { Accordion } from "@opencode-ai/ui/accordion" diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 495b323405..e65b575ac5 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -7,7 +7,7 @@ import { Keybind } from "@opencode-ai/ui/keybind" import { Spinner } from "@opencode-ai/ui/spinner" import { showToast } from "@opencode-ai/ui/toast" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" -import { getFilename } from "@opencode-ai/util/path" +import { getFilename } from "@opencode-ai/shared/util/path" import { createEffect, createMemo, For, onCleanup, Show } from "solid-js" import { createStore } from "solid-js/store" import { Portal } from "solid-js/web" diff --git a/packages/app/src/components/session/session-new-view.tsx b/packages/app/src/components/session/session-new-view.tsx index e4ef363936..d2cac28fc4 100644 --- a/packages/app/src/components/session/session-new-view.tsx +++ b/packages/app/src/components/session/session-new-view.tsx @@ -5,7 +5,7 @@ import { useSDK } from "@/context/sdk" import { useLanguage } from "@/context/language" import { Icon } from "@opencode-ai/ui/icon" import { Mark } from "@opencode-ai/ui/logo" -import { getDirectory, getFilename } from "@opencode-ai/util/path" +import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" const MAIN_WORKTREE = "main" const CREATE_WORKTREE = "create" diff --git a/packages/app/src/components/session/session-sortable-tab.tsx b/packages/app/src/components/session/session-sortable-tab.tsx index dfda91c160..fb2275c445 100644 --- a/packages/app/src/components/session/session-sortable-tab.tsx +++ b/packages/app/src/components/session/session-sortable-tab.tsx @@ -5,7 +5,7 @@ import { FileIcon } from "@opencode-ai/ui/file-icon" import { IconButton } from "@opencode-ai/ui/icon-button" import { TooltipKeybind } from "@opencode-ai/ui/tooltip" import { Tabs } from "@opencode-ai/ui/tabs" -import { getFilename } from "@opencode-ai/util/path" +import { getFilename } from "@opencode-ai/shared/util/path" import { useFile } from "@/context/file" import { useLanguage } from "@/context/language" import { useCommand } from "@/context/command" diff --git a/packages/app/src/context/file.tsx b/packages/app/src/context/file.tsx index f8fec7142d..8998731a6c 100644 --- a/packages/app/src/context/file.tsx +++ b/packages/app/src/context/file.tsx @@ -3,7 +3,7 @@ import { createStore, produce, reconcile } from "solid-js/store" import { createSimpleContext } from "@opencode-ai/ui/context" import { showToast } from "@opencode-ai/ui/toast" import { useParams } from "@solidjs/router" -import { getFilename } from "@opencode-ai/util/path" +import { getFilename } from "@opencode-ai/shared/util/path" import { useSDK } from "./sdk" import { useSync } from "./sync" import { useLanguage } from "@/context/language" diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 0cf3570a8b..fe5f2f1301 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -8,7 +8,7 @@ import type { Todo, } from "@opencode-ai/sdk/v2/client" import { showToast } from "@opencode-ai/ui/toast" -import { getFilename } from "@opencode-ai/util/path" +import { getFilename } from "@opencode-ai/shared/util/path" import { createContext, getOwner, onCleanup, onMount, type ParentProps, untrack, useContext } from "solid-js" import { createStore, produce, reconcile } from "solid-js/store" import { useLanguage } from "@/context/language" diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index c4deb431a7..ad987efa6e 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -11,8 +11,8 @@ import type { Todo, } from "@opencode-ai/sdk/v2/client" import { showToast } from "@opencode-ai/ui/toast" -import { getFilename } from "@opencode-ai/util/path" -import { retry } from "@opencode-ai/util/retry" +import { getFilename } from "@opencode-ai/shared/util/path" +import { retry } from "@opencode-ai/shared/util/retry" import { batch } from "solid-js" import { reconcile, type SetStoreFunction, type Store } from "solid-js/store" import type { State, VcsCache } from "./types" diff --git a/packages/app/src/context/global-sync/event-reducer.ts b/packages/app/src/context/global-sync/event-reducer.ts index 500013c1da..11a0cf83fd 100644 --- a/packages/app/src/context/global-sync/event-reducer.ts +++ b/packages/app/src/context/global-sync/event-reducer.ts @@ -1,4 +1,4 @@ -import { Binary } from "@opencode-ai/util/binary" +import { Binary } from "@opencode-ai/shared/util/binary" import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store" import type { Message, diff --git a/packages/app/src/context/local.tsx b/packages/app/src/context/local.tsx index 28ce2770de..0b0972ee67 100644 --- a/packages/app/src/context/local.tsx +++ b/packages/app/src/context/local.tsx @@ -1,5 +1,5 @@ import { createSimpleContext } from "@opencode-ai/ui/context" -import { base64Encode } from "@opencode-ai/util/encode" +import { base64Encode } from "@opencode-ai/shared/util/encode" import { useParams } from "@solidjs/router" import { batch, createEffect, createMemo } from "solid-js" import { createStore } from "solid-js/store" diff --git a/packages/app/src/context/notification.tsx b/packages/app/src/context/notification.tsx index 281a1ef33d..251b67b06c 100644 --- a/packages/app/src/context/notification.tsx +++ b/packages/app/src/context/notification.tsx @@ -7,8 +7,8 @@ import { useGlobalSync } from "./global-sync" import { usePlatform } from "@/context/platform" import { useLanguage } from "@/context/language" import { useSettings } from "@/context/settings" -import { Binary } from "@opencode-ai/util/binary" -import { base64Encode } from "@opencode-ai/util/encode" +import { Binary } from "@opencode-ai/shared/util/binary" +import { base64Encode } from "@opencode-ai/shared/util/encode" import { decode64 } from "@/utils/base64" import { EventSessionError } from "@opencode-ai/sdk/v2" import { Persist, persisted } from "@/utils/persist" diff --git a/packages/app/src/context/permission-auto-respond.test.ts b/packages/app/src/context/permission-auto-respond.test.ts index 7556113005..2f8ca6265e 100644 --- a/packages/app/src/context/permission-auto-respond.test.ts +++ b/packages/app/src/context/permission-auto-respond.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" import type { PermissionRequest, Session } from "@opencode-ai/sdk/v2/client" -import { base64Encode } from "@opencode-ai/util/encode" +import { base64Encode } from "@opencode-ai/shared/util/encode" import { autoRespondsPermission, isDirectoryAutoAccepting } from "./permission-auto-respond" const session = (input: { id: string; parentID?: string }) => diff --git a/packages/app/src/context/permission-auto-respond.ts b/packages/app/src/context/permission-auto-respond.ts index b206deedff..2ebca34347 100644 --- a/packages/app/src/context/permission-auto-respond.ts +++ b/packages/app/src/context/permission-auto-respond.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/util/encode" +import { base64Encode } from "@opencode-ai/shared/util/encode" export function acceptKey(sessionID: string, directory?: string) { if (!directory) return sessionID diff --git a/packages/app/src/context/prompt.tsx b/packages/app/src/context/prompt.tsx index 831fdbca83..9b666e5e75 100644 --- a/packages/app/src/context/prompt.tsx +++ b/packages/app/src/context/prompt.tsx @@ -1,5 +1,5 @@ import { createSimpleContext } from "@opencode-ai/ui/context" -import { checksum } from "@opencode-ai/util/encode" +import { checksum } from "@opencode-ai/shared/util/encode" import { useParams } from "@solidjs/router" import { batch, createMemo, createRoot, getOwner, onCleanup } from "solid-js" import { createStore, type SetStoreFunction } from "solid-js/store" diff --git a/packages/app/src/context/sync.tsx b/packages/app/src/context/sync.tsx index fb02a2d2d0..29b7fe68c5 100644 --- a/packages/app/src/context/sync.tsx +++ b/packages/app/src/context/sync.tsx @@ -1,7 +1,7 @@ import { batch, createMemo } from "solid-js" import { createStore, produce, reconcile } from "solid-js/store" -import { Binary } from "@opencode-ai/util/binary" -import { retry } from "@opencode-ai/util/retry" +import { Binary } from "@opencode-ai/shared/util/binary" +import { retry } from "@opencode-ai/shared/util/retry" import { createSimpleContext } from "@opencode-ai/ui/context" import { clearSessionPrefetch, diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx index 427b4823b5..f604dd6c5c 100644 --- a/packages/app/src/pages/directory-layout.tsx +++ b/packages/app/src/pages/directory-layout.tsx @@ -1,6 +1,6 @@ import { DataProvider } from "@opencode-ai/ui/context" import { showToast } from "@opencode-ai/ui/toast" -import { base64Encode } from "@opencode-ai/util/encode" +import { base64Encode } from "@opencode-ai/shared/util/encode" import { useLocation, useNavigate, useParams } from "@solidjs/router" import { createEffect, createMemo, type ParentProps, Show } from "solid-js" import { useLanguage } from "@/context/language" diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index 4c795b9683..46cacdf627 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -3,7 +3,7 @@ import { Button } from "@opencode-ai/ui/button" import { Logo } from "@opencode-ai/ui/logo" import { useLayout } from "@/context/layout" import { useNavigate } from "@solidjs/router" -import { base64Encode } from "@opencode-ai/util/encode" +import { base64Encode } from "@opencode-ai/shared/util/encode" import { Icon } from "@opencode-ai/ui/icon" import { usePlatform } from "@/context/platform" import { DateTime } from "luxon" diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index f402f4bc04..62d5cba615 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -17,7 +17,7 @@ import { useNavigate, useParams } from "@solidjs/router" import { useLayout, LocalProject } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" import { Persist, persisted } from "@/utils/persist" -import { base64Encode } from "@opencode-ai/util/encode" +import { base64Encode } from "@opencode-ai/shared/util/encode" import { decode64 } from "@/utils/base64" import { ResizeHandle } from "@opencode-ai/ui/resize-handle" import { Button } from "@opencode-ai/ui/button" @@ -25,7 +25,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button" import { Tooltip } from "@opencode-ai/ui/tooltip" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { Dialog } from "@opencode-ai/ui/dialog" -import { getFilename } from "@opencode-ai/util/path" +import { getFilename } from "@opencode-ai/shared/util/path" import { Session, type Message } from "@opencode-ai/sdk/v2/client" import { usePlatform } from "@/context/platform" import { useSettings } from "@/context/settings" @@ -48,8 +48,8 @@ import { } from "@/context/global-sync/session-prefetch" import { useNotification } from "@/context/notification" import { usePermission } from "@/context/permission" -import { Binary } from "@opencode-ai/util/binary" -import { retry } from "@opencode-ai/util/retry" +import { Binary } from "@opencode-ai/shared/util/binary" +import { retry } from "@opencode-ai/shared/util/retry" import { playSoundById } from "@/utils/sound" import { createAim } from "@/utils/aim" import { setNavigate } from "@/utils/notification-click" diff --git a/packages/app/src/pages/layout/helpers.ts b/packages/app/src/pages/layout/helpers.ts index 48158debba..26b66d1668 100644 --- a/packages/app/src/pages/layout/helpers.ts +++ b/packages/app/src/pages/layout/helpers.ts @@ -1,4 +1,4 @@ -import { getFilename } from "@opencode-ai/util/path" +import { getFilename } from "@opencode-ai/shared/util/path" import { type Session } from "@opencode-ai/sdk/v2/client" type SessionStore = { diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index e56accfc83..b0f45859a4 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -4,7 +4,7 @@ import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" import { Spinner } from "@opencode-ai/ui/spinner" import { Tooltip } from "@opencode-ai/ui/tooltip" -import { getFilename } from "@opencode-ai/util/path" +import { getFilename } from "@opencode-ai/shared/util/path" import { A, useParams } from "@solidjs/router" import { type Accessor, createMemo, For, type JSX, Match, Show, Switch } from "solid-js" import { useGlobalSync } from "@/context/global-sync" diff --git a/packages/app/src/pages/layout/sidebar-project.tsx b/packages/app/src/pages/layout/sidebar-project.tsx index 7c9ae1aafb..076e1ef88b 100644 --- a/packages/app/src/pages/layout/sidebar-project.tsx +++ b/packages/app/src/pages/layout/sidebar-project.tsx @@ -1,6 +1,6 @@ import { createMemo, For, Show, type Accessor, type JSX } from "solid-js" import { createStore } from "solid-js/store" -import { base64Encode } from "@opencode-ai/util/encode" +import { base64Encode } from "@opencode-ai/shared/util/encode" import { Button } from "@opencode-ai/ui/button" import { ContextMenu } from "@opencode-ai/ui/context-menu" import { HoverCard } from "@opencode-ai/ui/hover-card" diff --git a/packages/app/src/pages/layout/sidebar-workspace.tsx b/packages/app/src/pages/layout/sidebar-workspace.tsx index 878b6e5fa2..9e00691471 100644 --- a/packages/app/src/pages/layout/sidebar-workspace.tsx +++ b/packages/app/src/pages/layout/sidebar-workspace.tsx @@ -3,8 +3,8 @@ import { createEffect, createMemo, For, Show, type Accessor, type JSX } from "so import { createStore } from "solid-js/store" import { createSortable } from "@thisbeyond/solid-dnd" import { createMediaQuery } from "@solid-primitives/media" -import { base64Encode } from "@opencode-ai/util/encode" -import { getFilename } from "@opencode-ai/util/path" +import { base64Encode } from "@opencode-ai/shared/util/encode" +import { getFilename } from "@opencode-ai/shared/util/path" import { Button } from "@opencode-ai/ui/button" import { Collapsible } from "@opencode-ai/ui/collapsible" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index eb6a494119..e328e3f0cc 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -27,7 +27,7 @@ import { createAutoScroll } from "@opencode-ai/ui/hooks" import { previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge" import { Button } from "@opencode-ai/ui/button" import { showToast } from "@opencode-ai/ui/toast" -import { checksum } from "@opencode-ai/util/encode" +import { checksum } from "@opencode-ai/shared/util/encode" import { useSearchParams } from "@solidjs/router" import { NewSessionView, SessionHeader } from "@/components/session" import { useComments } from "@/context/comments" diff --git a/packages/app/src/pages/session/file-tabs.tsx b/packages/app/src/pages/session/file-tabs.tsx index cb76175236..a64dff64e2 100644 --- a/packages/app/src/pages/session/file-tabs.tsx +++ b/packages/app/src/pages/session/file-tabs.tsx @@ -6,7 +6,7 @@ import type { FileSearchHandle } from "@opencode-ai/ui/file" import { useFileComponent } from "@opencode-ai/ui/context/file" import { cloneSelectedLineRange, previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge" import { createLineCommentController } from "@opencode-ai/ui/line-comment-annotations" -import { sampledChecksum } from "@opencode-ai/util/encode" +import { sampledChecksum } from "@opencode-ai/shared/util/encode" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { IconButton } from "@opencode-ai/ui/icon-button" import { Tabs } from "@opencode-ai/ui/tabs" diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index eac425fa6c..978f188b6b 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -15,8 +15,8 @@ import { ScrollView } from "@opencode-ai/ui/scroll-view" import { TextField } from "@opencode-ai/ui/text-field" import type { AssistantMessage, Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2" import { showToast } from "@opencode-ai/ui/toast" -import { Binary } from "@opencode-ai/util/binary" -import { getFilename } from "@opencode-ai/util/path" +import { Binary } from "@opencode-ai/shared/util/binary" +import { getFilename } from "@opencode-ai/shared/util/path" import { Popover as KobaltePopover } from "@kobalte/core/popover" import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture" import { SessionContextUsage } from "@/components/session-context-usage" @@ -791,7 +791,7 @@ export function MessageTimeline(props: { data-slot="session-title-child" value={title.draft} disabled={titleMutation.isPending} - class="text-14-medium text-text-strong grow-1 min-w-0 rounded-[6px]" + class="text-14-medium text-text-strong grow-1 min-w-0 rounded-[6px] pl-1 -ml-1" style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }} onInput={(event) => setTitle("draft", event.currentTarget.value)} onKeyDown={(event) => { diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index 2397953737..b5d2544636 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -12,7 +12,7 @@ import { useSDK } from "@/context/sdk" import { useSync } from "@/context/sync" import { useTerminal } from "@/context/terminal" import { showToast } from "@opencode-ai/ui/toast" -import { findLast } from "@opencode-ai/util/array" +import { findLast } from "@opencode-ai/shared/util/array" import { createSessionTabs } from "@/pages/session/helpers" import { extractPromptFromParts } from "@/utils/prompt" import { UserMessage } from "@opencode-ai/sdk/v2" diff --git a/packages/app/src/utils/base64.ts b/packages/app/src/utils/base64.ts index c1f9d88c6e..f60dff2b6d 100644 --- a/packages/app/src/utils/base64.ts +++ b/packages/app/src/utils/base64.ts @@ -1,4 +1,4 @@ -import { base64Decode } from "@opencode-ai/util/encode" +import { base64Decode } from "@opencode-ai/shared/util/encode" export function decode64(value: string | undefined) { if (value === undefined) return diff --git a/packages/app/src/utils/persist.ts b/packages/app/src/utils/persist.ts index 3dcbeb7d36..dce0e94c3b 100644 --- a/packages/app/src/utils/persist.ts +++ b/packages/app/src/utils/persist.ts @@ -1,6 +1,6 @@ import { Platform, usePlatform } from "@/context/platform" import { makePersisted, type AsyncStorage, type SyncStorage } from "@solid-primitives/storage" -import { checksum } from "@opencode-ai/util/encode" +import { checksum } from "@opencode-ai/shared/util/encode" import { createResource, type Accessor } from "solid-js" import type { SetStoreFunction, Store } from "solid-js/store" diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 2c1fe6775a..062114eebd 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.4.4", + "version": "1.4.6", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts index 387a8503b1..11f76ca598 100644 --- a/packages/console/app/src/i18n/ar.ts +++ b/packages/console/app/src/i18n/ar.ts @@ -249,7 +249,7 @@ export const dict = { "go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع", "go.meta.description": - "يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.1 وGLM-5 و Kimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni و MiniMax M2.5 وMiniMax M2.7.", + "يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود طلب سخية لمدة 5 ساعات لـ GLM-5.1 وGLM-5 و Kimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni و Qwen3.5 Plus و Qwen3.6 Plus و MiniMax M2.5 وMiniMax M2.7.", "go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع", "go.hero.body": "يجلب Go البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر، حتى تتمكن من البناء باستخدام وكلاء أقوياء دون القلق بشأن التكلفة أو التوفر.", @@ -297,7 +297,8 @@ export const dict = { "go.problem.item1": "أسعار اشتراك منخفضة التكلفة", "go.problem.item2": "حدود سخية ووصول موثوق", "go.problem.item3": "مصمم لأكبر عدد ممكن من المبرمجين", - "go.problem.item4": "يتضمن GLM-5.1 وGLM-5 وKimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni وMiniMax M2.5 وMiniMax M2.7", + "go.problem.item4": + "يتضمن GLM-5.1 وGLM-5 وKimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7", "go.how.title": "كيف يعمل Go", "go.how.body": "يبدأ Go من $5 للشهر الأول، ثم $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.", "go.how.step1.title": "أنشئ حسابًا", @@ -318,11 +319,10 @@ export const dict = { "go.faq.q1": "ما هو OpenCode Go؟", "go.faq.a1": "Go هو اشتراك منخفض التكلفة يمنحك وصولًا موثوقًا إلى نماذج مفتوحة المصدر قادرة على البرمجة الوكيلة.", "go.faq.q2": "ما النماذج التي يتضمنها Go؟", - "go.faq.a2": - "يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni وMiniMax M2.5 وMiniMax M2.7، مع حدود سخية ووصول موثوق.", + "go.faq.a2": "يتضمن Go النماذج المدرجة أدناه، مع حدود سخية وإتاحة موثوقة.", "go.faq.q3": "هل Go هو نفسه Zen؟", "go.faq.a3": - "لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5.1 وGLM-5 و Kimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni و MiniMax M2.5 وMiniMax M2.7.", + "لا. Zen هو الدفع حسب الاستخدام، بينما يبدأ Go من $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى نماذج المصدر المفتوح GLM-5.1 وGLM-5 و Kimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni و Qwen3.5 Plus و Qwen3.6 Plus و MiniMax M2.5 وMiniMax M2.7.", "go.faq.q4": "كم تكلفة Go؟", "go.faq.a4.p1.beforePricing": "تكلفة Go", "go.faq.a4.p1.pricingLink": "$5 للشهر الأول", @@ -345,7 +345,7 @@ export const dict = { "go.faq.q9": "ما الفرق بين النماذج المجانية وGo؟", "go.faq.a9": - "تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni وMiniMax M2.5 وMiniMax M2.7 مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).", + "تشمل النماذج المجانية Big Pickle بالإضافة إلى النماذج الترويجية المتاحة في ذلك الوقت، مع حصة 200 طلب/يوم. يتضمن Go نماذج GLM-5.1 وGLM-5 وKimi K2.5 وMiMo-V2-Pro وMiMo-V2-Omni وQwen3.5 Plus وQwen3.6 Plus وMiniMax M2.5 وMiniMax M2.7 مع حصص طلبات أعلى مطبقة عبر نوافذ متجددة (5 ساعات، أسبوعيًا، وشهريًا)، تعادل تقريبًا 12 دولارًا كل 5 ساعات، و30 دولارًا في الأسبوع، و60 دولارًا في الشهر (تختلف أعداد الطلبات الفعلية حسب النموذج والاستخدام).", "zen.api.error.rateLimitExceeded": "تم تجاوز حد الطلبات. يرجى المحاولة مرة أخرى لاحقًا.", "zen.api.error.modelNotSupported": "النموذج {{model}} غير مدعوم", diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts index 33efd85d18..79f64485a1 100644 --- a/packages/console/app/src/i18n/br.ts +++ b/packages/console/app/src/i18n/br.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos", "go.meta.description": - "O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 e MiniMax M2.7.", + "O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de solicitação de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", "go.hero.title": "Modelos de codificação de baixo custo para todos", "go.hero.body": "O Go traz a codificação com agentes para programadores em todo o mundo. Oferecendo limites generosos e acesso confiável aos modelos de código aberto mais capazes, para que você possa construir com agentes poderosos sem se preocupar com custos ou disponibilidade.", @@ -302,7 +302,8 @@ export const dict = { "go.problem.item1": "Preço de assinatura de baixo custo", "go.problem.item2": "Limites generosos e acesso confiável", "go.problem.item3": "Feito para o maior número possível de programadores", - "go.problem.item4": "Inclui GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 e MiniMax M2.7", + "go.problem.item4": + "Inclui GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7", "go.how.title": "Como o Go funciona", "go.how.body": "O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.", @@ -325,11 +326,10 @@ export const dict = { "go.faq.a1": "Go é uma assinatura de baixo custo que oferece acesso confiável a modelos de código aberto capazes para codificação com agentes.", "go.faq.q2": "Quais modelos o Go inclui?", - "go.faq.a2": - "Go inclui GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 e MiniMax M2.7, com limites generosos e acesso confiável.", + "go.faq.a2": "O Go inclui os modelos listados abaixo, com limites generosos e acesso confiável.", "go.faq.q3": "O Go é o mesmo que o Zen?", "go.faq.a3": - "Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 e MiniMax M2.7.", + "Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável aos modelos open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", "go.faq.q4": "Quanto custa o Go?", "go.faq.a4.p1.beforePricing": "O Go custa", "go.faq.a4.p1.pricingLink": "$5 no primeiro mês", @@ -353,7 +353,7 @@ export const dict = { "go.faq.q9": "Qual a diferença entre os modelos gratuitos e o Go?", "go.faq.a9": - "Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go inclui GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 e MiniMax M2.7 com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).", + "Os modelos gratuitos incluem Big Pickle e modelos promocionais disponíveis no momento, com uma cota de 200 requisições/dia. O Go inclui GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 com cotas de requisição mais altas aplicadas em janelas móveis (5 horas, semanal e mensal), aproximadamente equivalentes a $12 por 5 horas, $30 por semana e $60 por mês (as contagens reais de requisições variam de acordo com o modelo e o uso).", "zen.api.error.rateLimitExceeded": "Limite de taxa excedido. Por favor, tente novamente mais tarde.", "zen.api.error.modelNotSupported": "Modelo {{model}} não suportado", diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts index abcdef2705..97b148c066 100644 --- a/packages/console/app/src/i18n/da.ts +++ b/packages/console/app/src/i18n/da.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle", "go.meta.description": - "Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 og MiniMax M2.7.", + "Go starter ved $5 for den første måned, derefter $10/måned, med generøse 5-timers anmodningsgrænser for GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", "go.hero.title": "Kodningsmodeller til lav pris for alle", "go.hero.body": "Go bringer agentisk kodning til programmører over hele verden. Med generøse grænser og pålidelig adgang til de mest kapable open source-modeller, så du kan bygge med kraftfulde agenter uden at bekymre dig om omkostninger eller tilgængelighed.", @@ -299,7 +299,8 @@ export const dict = { "go.problem.item1": "Lavpris abonnementspriser", "go.problem.item2": "Generøse grænser og pålidelig adgang", "go.problem.item3": "Bygget til så mange programmører som muligt", - "go.problem.item4": "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 og MiniMax M2.7", + "go.problem.item4": + "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7", "go.how.title": "Hvordan Go virker", "go.how.body": "Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.", @@ -322,11 +323,10 @@ export const dict = { "go.faq.a1": "Go er et lavprisabonnement, der giver dig pålidelig adgang til kapable open source-modeller til agentisk kodning.", "go.faq.q2": "Hvilke modeller inkluderer Go?", - "go.faq.a2": - "Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 og MiniMax M2.7, med generøse grænser og pålidelig adgang.", + "go.faq.a2": "Go inkluderer modellerne nedenfor med generøse grænser og pålidelig adgang.", "go.faq.q3": "Er Go det samme som Zen?", "go.faq.a3": - "Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 og MiniMax M2.7.", + "Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til open source-modellerne GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", "go.faq.q4": "Hvad koster Go?", "go.faq.a4.p1.beforePricing": "Go koster", "go.faq.a4.p1.pricingLink": "$5 første måned", @@ -349,7 +349,7 @@ export const dict = { "go.faq.q9": "Hvad er forskellen på gratis modeller og Go?", "go.faq.a9": - "Gratis modeller inkluderer Big Pickle plus salgsfremmende modeller tilgængelige på det tidspunkt, med en kvote på 200 forespørgsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 og MiniMax M2.7 med højere anmodningskvoter håndhævet over rullende vinduer (5-timers, ugentlig og månedlig), nogenlunde svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (faktiske anmodningstal varierer efter model og brug).", + "Gratis modeller inkluderer Big Pickle plus salgsfremmende modeller tilgængelige på det tidspunkt, med en kvote på 200 forespørgsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med højere anmodningskvoter håndhævet over rullende vinduer (5-timers, ugentlig og månedlig), nogenlunde svarende til $12 pr. 5 timer, $30 pr. uge og $60 pr. måned (faktiske anmodningstal varierer efter model og brug).", "zen.api.error.rateLimitExceeded": "Hastighedsgrænse overskredet. Prøv venligst igen senere.", "zen.api.error.modelNotSupported": "Model {{model}} understøttes ikke", diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts index cacd450fa0..1c5f631aea 100644 --- a/packages/console/app/src/i18n/de.ts +++ b/packages/console/app/src/i18n/de.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle", "go.meta.description": - "Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 und MiniMax M2.7.", + "Go beginnt bei $5 für den ersten Monat, danach $10/Monat, mit großzügigen 5-Stunden-Anfragelimits für GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.", "go.hero.title": "Kostengünstige Coding-Modelle für alle", "go.hero.body": "Go bringt Agentic Coding zu Programmierern auf der ganzen Welt. Mit großzügigen Limits und zuverlässigem Zugang zu den leistungsfähigsten Open-Source-Modellen, damit du mit leistungsstarken Agenten entwickeln kannst, ohne dir Gedanken über Kosten oder Verfügbarkeit zu machen.", @@ -301,7 +301,8 @@ export const dict = { "go.problem.item1": "Kostengünstiges Abonnement", "go.problem.item2": "Großzügige Limits und zuverlässiger Zugang", "go.problem.item3": "Für so viele Programmierer wie möglich gebaut", - "go.problem.item4": "Beinhaltet GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 und MiniMax M2.7", + "go.problem.item4": + "Beinhaltet GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7", "go.how.title": "Wie Go funktioniert", "go.how.body": "Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.", @@ -324,11 +325,10 @@ export const dict = { "go.faq.a1": "Go ist ein kostengünstiges Abonnement, das dir zuverlässigen Zugang zu leistungsfähigen Open-Source-Modellen für Agentic Coding bietet.", "go.faq.q2": "Welche Modelle beinhaltet Go?", - "go.faq.a2": - "Go beinhaltet GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 und MiniMax M2.7, mit großzügigen Limits und zuverlässigem Zugang.", + "go.faq.a2": "Go umfasst die unten aufgeführten Modelle mit großzügigen Limits und zuverlässigem Zugriff.", "go.faq.q3": "Ist Go dasselbe wie Zen?", "go.faq.a3": - "Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 und MiniMax M2.7.", + "Nein. Zen ist Pay-as-you-go, während Go bei $5 für den ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu den Open-Source-Modellen GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7.", "go.faq.q4": "Wie viel kostet Go?", "go.faq.a4.p1.beforePricing": "Go kostet", "go.faq.a4.p1.pricingLink": "$5 im ersten Monat", @@ -352,7 +352,7 @@ export const dict = { "go.faq.q9": "Was ist der Unterschied zwischen kostenlosen Modellen und Go?", "go.faq.a9": - "Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go beinhaltet GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 und MiniMax M2.7 mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).", + "Kostenlose Modelle beinhalten Big Pickle sowie Werbemodelle, die zum jeweiligen Zeitpunkt verfügbar sind, mit einem Kontingent von 200 Anfragen/Tag. Go beinhaltet GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 und MiniMax M2.7 mit höheren Anfragekontingenten, die über rollierende Zeitfenster (5 Stunden, wöchentlich und monatlich) durchgesetzt werden, grob äquivalent zu $12 pro 5 Stunden, $30 pro Woche und $60 pro Monat (tatsächliche Anfragezahlen variieren je nach Modell und Nutzung).", "zen.api.error.rateLimitExceeded": "Ratenlimit überschritten. Bitte versuche es später erneut.", "zen.api.error.modelNotSupported": "Modell {{model}} wird nicht unterstützt", diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index 2de627b478..d21d0dc9e0 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -248,7 +248,7 @@ export const dict = { "go.title": "OpenCode Go | Low cost coding models for everyone", "go.meta.description": - "Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5, and MiniMax M2.7.", + "Go starts at $5 for your first month, then $10/month, with generous 5-hour request limits for GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.", "go.hero.title": "Low cost coding models for everyone", "go.hero.body": "Go brings agentic coding to programmers around the world. Offering generous limits and reliable access to the most capable open-source models, so you can build with powerful agents without worrying about cost or availability.", @@ -295,7 +295,8 @@ export const dict = { "go.problem.item1": "Low cost subscription pricing", "go.problem.item2": "Generous limits and reliable access", "go.problem.item3": "Built for as many programmers as possible", - "go.problem.item4": "Includes GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5, and MiniMax M2.7", + "go.problem.item4": + "Includes GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7", "go.how.title": "How Go works", "go.how.body": "Go starts at $5 for your first month, then $10/month. You can use it with OpenCode or any agent.", "go.how.step1.title": "Create an account", @@ -317,11 +318,10 @@ export const dict = { "go.faq.a1": "Go is a low-cost subscription that gives you reliable access to capable open-source models for agentic coding.", "go.faq.q2": "What models does Go include?", - "go.faq.a2": - "Go includes GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5, and MiniMax M2.7, with generous limits and reliable access.", + "go.faq.a2": "Go includes the models listed below, with generous limits and reliable access.", "go.faq.q3": "Is Go the same as Zen?", "go.faq.a3": - "No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5, and MiniMax M2.7.", + "No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to open-source models GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7.", "go.faq.q4": "How much does Go cost?", "go.faq.a4.p1.beforePricing": "Go costs", "go.faq.a4.p1.pricingLink": "$5 first month", @@ -345,7 +345,7 @@ export const dict = { "go.faq.q9": "What is the difference between free models and Go?", "go.faq.a9": - "Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5, and MiniMax M2.7 with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).", + "Free models include Big Pickle plus promotional models available at the time, with a quota of 200 requests/day. Go includes GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, and MiniMax M2.7 with higher request quotas enforced across rolling windows (5-hour, weekly, and monthly), roughly equivalent to $12 per 5 hours, $30 per week, and $60 per month (actual request counts vary by model and usage).", "zen.api.error.rateLimitExceeded": "Rate limit exceeded. Please try again later.", "zen.api.error.modelNotSupported": "Model {{model}} not supported", diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts index 065f67a722..d6449fd954 100644 --- a/packages/console/app/src/i18n/es.ts +++ b/packages/console/app/src/i18n/es.ts @@ -254,7 +254,7 @@ export const dict = { "go.title": "OpenCode Go | Modelos de programación de bajo coste para todos", "go.meta.description": - "Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 y MiniMax M2.7.", + "Go comienza en $5 el primer mes, luego 10 $/mes, con generosos límites de solicitudes de 5 horas para GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.", "go.hero.title": "Modelos de programación de bajo coste para todos", "go.hero.body": "Go lleva la programación agéntica a programadores de todo el mundo. Ofrece límites generosos y acceso fiable a los modelos de código abierto más capaces, para que puedas crear con agentes potentes sin preocuparte por el coste o la disponibilidad.", @@ -303,7 +303,8 @@ export const dict = { "go.problem.item1": "Precios de suscripción de bajo coste", "go.problem.item2": "Límites generosos y acceso fiable", "go.problem.item3": "Creado para tantos programadores como sea posible", - "go.problem.item4": "Incluye GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 y MiniMax M2.7", + "go.problem.item4": + "Incluye GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7", "go.how.title": "Cómo funciona Go", "go.how.body": "Go comienza en $5 el primer mes, luego 10 $/mes. Puedes usarlo con OpenCode o cualquier agente.", "go.how.step1.title": "Crear una cuenta", @@ -325,11 +326,10 @@ export const dict = { "go.faq.a1": "Go es una suscripción de bajo coste que te da acceso fiable a modelos de código abierto capaces para programación agéntica.", "go.faq.q2": "¿Qué modelos incluye Go?", - "go.faq.a2": - "Go incluye GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 y MiniMax M2.7, con límites generosos y acceso fiable.", + "go.faq.a2": "Go incluye los modelos que se indican abajo, con límites generosos y acceso confiable.", "go.faq.q3": "¿Es Go lo mismo que Zen?", "go.faq.a3": - "No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 y MiniMax M2.7.", + "No. Zen es pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a los modelos de código abierto GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7.", "go.faq.q4": "¿Cuánto cuesta Go?", "go.faq.a4.p1.beforePricing": "Go cuesta", "go.faq.a4.p1.pricingLink": "$5 el primer mes", @@ -353,7 +353,7 @@ export const dict = { "go.faq.q9": "¿Cuál es la diferencia entre los modelos gratuitos y Go?", "go.faq.a9": - "Los modelos gratuitos incluyen Big Pickle más modelos promocionales disponibles en el momento, con una cuota de 200 solicitudes/día. Go incluye GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 y MiniMax M2.7 con cuotas de solicitud más altas aplicadas a través de ventanas móviles (5 horas, semanal y mensual), aproximadamente equivalente a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (los recuentos reales de solicitudes varían según el modelo y el uso).", + "Los modelos gratuitos incluyen Big Pickle más modelos promocionales disponibles en el momento, con una cuota de 200 solicitudes/día. Go incluye GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 y MiniMax M2.7 con cuotas de solicitud más altas aplicadas a través de ventanas móviles (5 horas, semanal y mensual), aproximadamente equivalente a 12 $ por 5 horas, 30 $ por semana y 60 $ por mes (los recuentos reales de solicitudes varían según el modelo y el uso).", "zen.api.error.rateLimitExceeded": "Límite de tasa excedido. Por favor, inténtalo de nuevo más tarde.", "zen.api.error.modelNotSupported": "Modelo {{model}} no soportado", diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts index 7d280f8b7e..c54c431040 100644 --- a/packages/console/app/src/i18n/fr.ts +++ b/packages/console/app/src/i18n/fr.ts @@ -255,7 +255,7 @@ export const dict = { "go.title": "OpenCode Go | Modèles de code à faible coût pour tous", "go.meta.description": - "Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites de requêtes généreuses sur 5 heures pour GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 et MiniMax M2.7.", + "Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites de requêtes généreuses sur 5 heures pour GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.", "go.hero.title": "Modèles de code à faible coût pour tous", "go.hero.body": "Go apporte le codage agentique aux programmeurs du monde entier. Offrant des limites généreuses et un accès fiable aux modèles open source les plus capables, pour que vous puissiez construire avec des agents puissants sans vous soucier du coût ou de la disponibilité.", @@ -303,7 +303,8 @@ export const dict = { "go.problem.item1": "Prix d'abonnement bas", "go.problem.item2": "Limites généreuses et accès fiable", "go.problem.item3": "Conçu pour autant de programmeurs que possible", - "go.problem.item4": "Inclut GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 et MiniMax M2.7", + "go.problem.item4": + "Inclut GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7", "go.how.title": "Comment fonctionne Go", "go.how.body": "Go commence à $5 pour le premier mois, puis 10 $/mois. Vous pouvez l'utiliser avec OpenCode ou n'importe quel agent.", @@ -326,11 +327,10 @@ export const dict = { "go.faq.a1": "Go est un abonnement à faible coût qui vous donne un accès fiable à des modèles open source performants pour le codage agentique.", "go.faq.q2": "Quels modèles Go inclut-il ?", - "go.faq.a2": - "Go inclut GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 et MiniMax M2.7, avec des limites généreuses et un accès fiable.", + "go.faq.a2": "Go inclut les modèles ci-dessous, avec des limites généreuses et un accès fiable.", "go.faq.q3": "Est-ce que Go est la même chose que Zen ?", "go.faq.a3": - "Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable aux modèles open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 et MiniMax M2.7.", + "Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable aux modèles open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7.", "go.faq.q4": "Combien coûte Go ?", "go.faq.a4.p1.beforePricing": "Go coûte", "go.faq.a4.p1.pricingLink": "$5 le premier mois", @@ -353,7 +353,7 @@ export const dict = { "Oui, vous pouvez utiliser Go avec n'importe quel agent. Suivez les instructions de configuration dans votre agent de code préféré.", "go.faq.q9": "Quelle est la différence entre les modèles gratuits et Go ?", "go.faq.a9": - "Les modèles gratuits incluent Big Pickle ainsi que des modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go inclut GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 et MiniMax M2.7 avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalent à 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois (le nombre réel de requêtes varie selon le modèle et l'utilisation).", + "Les modèles gratuits incluent Big Pickle ainsi que des modèles promotionnels disponibles à ce moment-là, avec un quota de 200 requêtes/jour. Go inclut GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 et MiniMax M2.7 avec des quotas de requêtes plus élevés appliqués sur des fenêtres glissantes (5 heures, hebdomadaire et mensuelle), à peu près équivalent à 12 $ par 5 heures, 30 $ par semaine et 60 $ par mois (le nombre réel de requêtes varie selon le modèle et l'utilisation).", "zen.api.error.rateLimitExceeded": "Limite de débit dépassée. Veuillez réessayer plus tard.", "zen.api.error.modelNotSupported": "Modèle {{model}} non pris en charge", diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts index 80daeeb5d9..aadea6fec0 100644 --- a/packages/console/app/src/i18n/it.ts +++ b/packages/console/app/src/i18n/it.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Modelli di coding a basso costo per tutti", "go.meta.description": - "Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 e MiniMax M2.7.", + "Go inizia a $5 per il primo mese, poi $10/mese, con generosi limiti di richiesta di 5 ore per GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", "go.hero.title": "Modelli di coding a basso costo per tutti", "go.hero.body": "Go porta il coding agentico ai programmatori di tutto il mondo. Offrendo limiti generosi e un accesso affidabile ai modelli open source più capaci, in modo da poter costruire con agenti potenti senza preoccuparsi dei costi o della disponibilità.", @@ -299,7 +299,8 @@ export const dict = { "go.problem.item1": "Prezzo di abbonamento a basso costo", "go.problem.item2": "Limiti generosi e accesso affidabile", "go.problem.item3": "Costruito per il maggior numero possibile di programmatori", - "go.problem.item4": "Include GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 e MiniMax M2.7", + "go.problem.item4": + "Include GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7", "go.how.title": "Come funziona Go", "go.how.body": "Go inizia a $5 per il primo mese, poi $10/mese. Puoi usarlo con OpenCode o qualsiasi agente.", "go.how.step1.title": "Crea un account", @@ -321,11 +322,10 @@ export const dict = { "go.faq.a1": "Go è un abbonamento a basso costo che ti dà un accesso affidabile a modelli open source capaci per il coding agentico.", "go.faq.q2": "Quali modelli include Go?", - "go.faq.a2": - "Go include GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 e MiniMax M2.7, con limiti generosi e accesso affidabile.", + "go.faq.a2": "Go include i modelli elencati di seguito, con limiti generosi e accesso affidabile.", "go.faq.q3": "Go è lo stesso di Zen?", "go.faq.a3": - "No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e accesso affidabile ai modelli open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 e MiniMax M2.7.", + "No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e accesso affidabile ai modelli open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7.", "go.faq.q4": "Quanto costa Go?", "go.faq.a4.p1.beforePricing": "Go costa", "go.faq.a4.p1.pricingLink": "$5 il primo mese", @@ -349,7 +349,7 @@ export const dict = { "go.faq.q9": "Qual è la differenza tra i modelli gratuiti e Go?", "go.faq.a9": - "I modelli gratuiti includono Big Pickle più modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go include GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 e MiniMax M2.7 con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a $12 ogni 5 ore, $30 a settimana e $60 al mese (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).", + "I modelli gratuiti includono Big Pickle più modelli promozionali disponibili al momento, con una quota di 200 richieste/giorno. Go include GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 e MiniMax M2.7 con quote di richiesta più elevate applicate su finestre mobili (5 ore, settimanale e mensile), approssimativamente equivalenti a $12 ogni 5 ore, $30 a settimana e $60 al mese (il conteggio effettivo delle richieste varia in base al modello e all'utilizzo).", "zen.api.error.rateLimitExceeded": "Limite di richieste superato. Riprova più tardi.", "zen.api.error.modelNotSupported": "Modello {{model}} non supportato", diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts index 16fecce6d2..f3b4c083e9 100644 --- a/packages/console/app/src/i18n/ja.ts +++ b/packages/console/app/src/i18n/ja.ts @@ -250,7 +250,7 @@ export const dict = { "go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル", "go.meta.description": - "Goは最初の月$5、その後$10/月で、GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5、MiniMax M2.7に対して5時間のゆとりあるリクエスト上限があります。", + "Goは最初の月$5、その後$10/月で、GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7に対して5時間のゆとりあるリクエスト上限があります。", "go.hero.title": "すべての人のための低価格なコーディングモデル", "go.hero.body": "Goは、世界中のプログラマーにエージェント型コーディングをもたらします。最も高性能なオープンソースモデルへの十分な制限と安定したアクセスを提供し、コストや可用性を気にすることなく強力なエージェントで構築できます。", @@ -299,7 +299,8 @@ export const dict = { "go.problem.item1": "低価格なサブスクリプション料金", "go.problem.item2": "十分な制限と安定したアクセス", "go.problem.item3": "できるだけ多くのプログラマーのために構築", - "go.problem.item4": "GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5、MiniMax M2.7を含む", + "go.problem.item4": + "GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7を含む", "go.how.title": "Goの仕組み", "go.how.body": "Goは最初の月$5、その後$10/月で始まります。OpenCodeまたは任意のエージェントで使えます。", "go.how.step1.title": "アカウントを作成", @@ -321,11 +322,10 @@ export const dict = { "go.faq.a1": "Goは、エージェント型コーディングのための有能なオープンソースモデルへの安定したアクセスを提供する低価格なサブスクリプションです。", "go.faq.q2": "Goにはどのモデルが含まれますか?", - "go.faq.a2": - "Goには、GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5、MiniMax M2.7が含まれており、十分な制限と安定したアクセスが提供されます。", + "go.faq.a2": "Go には、十分な利用上限と安定したアクセスを備えた、以下のモデルが含まれます。", "go.faq.q3": "GoはZenと同じですか?", "go.faq.a3": - "いいえ。Zenは従量課金制ですが、Goは最初の月$5、その後$10/月で始まり、GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5、MiniMax M2.7のオープンソースモデルに対して、ゆとりある上限と信頼できるアクセスを提供します。", + "いいえ。Zenは従量課金制ですが、Goは最初の月$5、その後$10/月で始まり、GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7のオープンソースモデルに対して、ゆとりある上限と信頼できるアクセスを提供します。", "go.faq.q4": "Goの料金は?", "go.faq.a4.p1.beforePricing": "Goは", "go.faq.a4.p1.pricingLink": "最初の月$5", @@ -349,7 +349,7 @@ export const dict = { "go.faq.q9": "無料モデルとGoの違いは何ですか?", "go.faq.a9": - "無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。GoにはGLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5、MiniMax M2.7が含まれ、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。これは概算で5時間あたり$12、週間$30、月間$60相当です(実際のリクエスト数はモデルと使用状況により異なります)。", + "無料モデルにはBig Pickleと、その時点で利用可能なプロモーションモデルが含まれ、1日200リクエストの制限があります。GoにはGLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5、MiniMax M2.7が含まれ、ローリングウィンドウ(5時間、週間、月間)全体でより高いリクエスト制限が適用されます。これは概算で5時間あたり$12、週間$30、月間$60相当です(実際のリクエスト数はモデルと使用状況により異なります)。", "zen.api.error.rateLimitExceeded": "レート制限を超えました。後でもう一度お試しください。", "zen.api.error.modelNotSupported": "モデル {{model}} はサポートされていません", diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts index 11e76470d1..e2320c359f 100644 --- a/packages/console/app/src/i18n/ko.ts +++ b/packages/console/app/src/i18n/ko.ts @@ -247,7 +247,7 @@ export const dict = { "go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델", "go.meta.description": - "Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5, MiniMax M2.7에 대해 넉넉한 5시간 요청 한도를 제공합니다.", + "Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7에 대해 넉넉한 5시간 요청 한도를 제공합니다.", "go.hero.title": "모두를 위한 저비용 코딩 모델", "go.hero.body": "Go는 전 세계 프로그래머들에게 에이전트 코딩을 제공합니다. 가장 유능한 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공하므로, 비용이나 가용성 걱정 없이 강력한 에이전트로 빌드할 수 있습니다.", @@ -296,7 +296,8 @@ export const dict = { "go.problem.item1": "저렴한 구독 가격", "go.problem.item2": "넉넉한 한도와 안정적인 액세스", "go.problem.item3": "가능한 한 많은 프로그래머를 위해 제작됨", - "go.problem.item4": "GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5, MiniMax M2.7 포함", + "go.problem.item4": + "GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 포함", "go.how.title": "Go 작동 방식", "go.how.body": "Go는 첫 달 $5, 이후 $10/월로 시작합니다. OpenCode 또는 어떤 에이전트와도 함께 사용할 수 있습니다.", "go.how.step1.title": "계정 생성", @@ -317,11 +318,10 @@ export const dict = { "go.faq.q1": "OpenCode Go란 무엇인가요?", "go.faq.a1": "Go는 에이전트 코딩을 위한 유능한 오픈 소스 모델에 대해 안정적인 액세스를 제공하는 저비용 구독입니다.", "go.faq.q2": "Go에는 어떤 모델이 포함되나요?", - "go.faq.a2": - "Go에는 넉넉한 한도와 안정적인 액세스를 제공하는 GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5, MiniMax M2.7가 포함됩니다.", + "go.faq.a2": "Go에는 넉넉한 한도와 안정적인 액세스를 제공하는 아래 모델이 포함됩니다.", "go.faq.q3": "Go는 Zen과 같은가요?", "go.faq.a3": - "아니요. Zen은 종량제인 반면, Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5, MiniMax M2.7 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.", + "아니요. Zen은 종량제인 반면, Go는 첫 달 $5, 이후 $10/월로 시작하며, GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.", "go.faq.q4": "Go 비용은 얼마인가요?", "go.faq.a4.p1.beforePricing": "Go 비용은", "go.faq.a4.p1.pricingLink": "첫 달 $5", @@ -344,7 +344,7 @@ export const dict = { "go.faq.q9": "무료 모델과 Go의 차이점은 무엇인가요?", "go.faq.a9": - "무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5, MiniMax M2.7를 포함하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 이는 대략 5시간당 $12, 주당 $30, 월 $60에 해당합니다(실제 요청 수는 모델 및 사용량에 따라 다름).", + "무료 모델에는 Big Pickle과 당시 사용 가능한 프로모션 모델이 포함되며, 하루 200회 요청 할당량이 적용됩니다. Go는 GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5, MiniMax M2.7를 포함하며, 롤링 윈도우(5시간, 주간, 월간)에 걸쳐 더 높은 요청 할당량을 적용합니다. 이는 대략 5시간당 $12, 주당 $30, 월 $60에 해당합니다(실제 요청 수는 모델 및 사용량에 따라 다름).", "zen.api.error.rateLimitExceeded": "속도 제한을 초과했습니다. 나중에 다시 시도해 주세요.", "zen.api.error.modelNotSupported": "{{model}} 모델은 지원되지 않습니다", diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts index 282208e5e1..717c2ad1ec 100644 --- a/packages/console/app/src/i18n/no.ts +++ b/packages/console/app/src/i18n/no.ts @@ -251,7 +251,7 @@ export const dict = { "go.title": "OpenCode Go | Rimelige kodemodeller for alle", "go.meta.description": - "Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 og MiniMax M2.7.", + "Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse 5-timers forespørselsgrenser for GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", "go.hero.title": "Rimelige kodemodeller for alle", "go.hero.body": "Go bringer agent-koding til programmerere over hele verden. Med rause grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene, kan du bygge med kraftige agenter uten å bekymre deg for kostnader eller tilgjengelighet.", @@ -299,7 +299,8 @@ export const dict = { "go.problem.item1": "Rimelig abonnementspris", "go.problem.item2": "Rause grenser og pålitelig tilgang", "go.problem.item3": "Bygget for så mange programmerere som mulig", - "go.problem.item4": "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 og MiniMax M2.7", + "go.problem.item4": + "Inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7", "go.how.title": "Hvordan Go fungerer", "go.how.body": "Go starter på $5 for den første måneden, deretter $10/måned. Du kan bruke det med OpenCode eller hvilken som helst agent.", @@ -322,11 +323,10 @@ export const dict = { "go.faq.a1": "Go er et rimelig abonnement som gir deg pålitelig tilgang til kapable åpen kildekode-modeller for agent-koding.", "go.faq.q2": "Hvilke modeller inkluderer Go?", - "go.faq.a2": - "Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 og MiniMax M2.7, med rause grenser og pålitelig tilgang.", + "go.faq.a2": "Go inkluderer modellene nedenfor, med høye grenser og pålitelig tilgang.", "go.faq.q3": "Er Go det samme som Zen?", "go.faq.a3": - "Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til åpen kildekode-modellene GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 og MiniMax M2.7.", + "Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til åpen kildekode-modellene GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7.", "go.faq.q4": "Hva koster Go?", "go.faq.a4.p1.beforePricing": "Go koster", "go.faq.a4.p1.pricingLink": "$5 første måned", @@ -350,7 +350,7 @@ export const dict = { "go.faq.q9": "Hva er forskjellen mellom gratis modeller og Go?", "go.faq.a9": - "Gratis modeller inkluderer Big Pickle pluss kampanjemodeller tilgjengelig på det tidspunktet, med en kvote på 200 forespørsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 og MiniMax M2.7 med høyere kvoter håndhevet over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende $12 per 5 timer, $30 per uke og $60 per måned (faktiske forespørselsantall varierer etter modell og bruk).", + "Gratis modeller inkluderer Big Pickle pluss kampanjemodeller tilgjengelig på det tidspunktet, med en kvote på 200 forespørsler/dag. Go inkluderer GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 og MiniMax M2.7 med høyere kvoter håndhevet over rullerende vinduer (5 timer, ukentlig og månedlig), omtrent tilsvarende $12 per 5 timer, $30 per uke og $60 per måned (faktiske forespørselsantall varierer etter modell og bruk).", "zen.api.error.rateLimitExceeded": "Rate limit overskredet. Vennligst prøv igjen senere.", "zen.api.error.modelNotSupported": "Modell {{model}} støttes ikke", diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts index ffdd8f5cbd..c9daeedee3 100644 --- a/packages/console/app/src/i18n/pl.ts +++ b/packages/console/app/src/i18n/pl.ts @@ -252,7 +252,7 @@ export const dict = { "go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego", "go.meta.description": - "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 i MiniMax M2.7.", + "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi 5-godzinnymi limitami zapytań dla GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.", "go.hero.title": "Niskokosztowe modele do kodowania dla każdego", "go.hero.body": "Go udostępnia programowanie z agentami programistom na całym świecie. Oferuje hojne limity i niezawodny dostęp do najzdolniejszych modeli open source, dzięki czemu możesz budować za pomocą potężnych agentów, nie martwiąc się o koszty czy dostępność.", @@ -300,7 +300,8 @@ export const dict = { "go.problem.item1": "Niskokosztowa cena subskrypcji", "go.problem.item2": "Hojne limity i niezawodny dostęp", "go.problem.item3": "Stworzony dla jak największej liczby programistów", - "go.problem.item4": "Zawiera GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 i MiniMax M2.7", + "go.problem.item4": + "Zawiera GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7", "go.how.title": "Jak działa Go", "go.how.body": "Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc. Możesz go używać z OpenCode lub dowolnym agentem.", @@ -323,11 +324,10 @@ export const dict = { "go.faq.a1": "Go to niskokosztowa subskrypcja, która daje niezawodny dostęp do zdolnych modeli open source dla agentów kodujących.", "go.faq.q2": "Jakie modele zawiera Go?", - "go.faq.a2": - "Go zawiera GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 i MiniMax M2.7, z hojnymi limitami i niezawodnym dostępem.", + "go.faq.a2": "Go obejmuje poniższe modele z wysokimi limitami i niezawodnym dostępem.", "go.faq.q3": "Czy Go to to samo co Zen?", "go.faq.a3": - "Nie. Zen to model płatności za użycie, podczas gdy Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi limitami i niezawodnym dostępem do modeli open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 i MiniMax M2.7.", + "Nie. Zen to model płatności za użycie, podczas gdy Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc, z hojnymi limitami i niezawodnym dostępem do modeli open source GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7.", "go.faq.q4": "Ile kosztuje Go?", "go.faq.a4.p1.beforePricing": "Go kosztuje", "go.faq.a4.p1.pricingLink": "$5 za pierwszy miesiąc", @@ -351,7 +351,7 @@ export const dict = { "go.faq.q9": "Jaka jest różnica między darmowymi modelami a Go?", "go.faq.a9": - "Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go zawiera GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 i MiniMax M2.7 z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), w przybliżeniu równoważnymi $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie (rzeczywista liczba zapytań zależy od modelu i użycia).", + "Darmowe modele obejmują Big Pickle oraz modele promocyjne dostępne w danym momencie, z limitem 200 zapytań/dzień. Go zawiera GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 i MiniMax M2.7 z wyższymi limitami zapytań egzekwowanymi w oknach kroczących (5-godzinnych, tygodniowych i miesięcznych), w przybliżeniu równoważnymi $12 na 5 godzin, $30 tygodniowo i $60 miesięcznie (rzeczywista liczba zapytań zależy od modelu i użycia).", "zen.api.error.rateLimitExceeded": "Przekroczono limit zapytań. Spróbuj ponownie później.", "zen.api.error.modelNotSupported": "Model {{model}} nie jest obsługiwany", diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts index 8864b6a272..01baa89852 100644 --- a/packages/console/app/src/i18n/ru.ts +++ b/packages/console/app/src/i18n/ru.ts @@ -255,7 +255,7 @@ export const dict = { "go.title": "OpenCode Go | Недорогие модели для кодинга для всех", "go.meta.description": - "Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 и MiniMax M2.7.", + "Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами запросов за 5 часов для GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.", "go.hero.title": "Недорогие модели для кодинга для всех", "go.hero.body": "Go открывает доступ к агентам-программистам разработчикам по всему миру. Предлагая щедрые лимиты и надежный доступ к наиболее способным моделям с открытым исходным кодом, вы можете создавать проекты с мощными агентами, не беспокоясь о затратах или доступности.", @@ -304,7 +304,8 @@ export const dict = { "go.problem.item1": "Недорогая подписка", "go.problem.item2": "Щедрые лимиты и надежный доступ", "go.problem.item3": "Создан для максимального числа программистов", - "go.problem.item4": "Включает GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 и MiniMax M2.7", + "go.problem.item4": + "Включает GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7", "go.how.title": "Как работает Go", "go.how.body": "Go начинается с $5 за первый месяц, затем $10/месяц. Вы можете использовать его с OpenCode или любым агентом.", @@ -327,11 +328,10 @@ export const dict = { "go.faq.a1": "Go — это недорогая подписка, дающая надежный доступ к мощным моделям с открытым исходным кодом для агентов-программистов.", "go.faq.q2": "Какие модели включает Go?", - "go.faq.a2": - "Go включает GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 и MiniMax M2.7, с щедрыми лимитами и надежным доступом.", + "go.faq.a2": "Go включает перечисленные ниже модели с щедрыми лимитами и надежным доступом.", "go.faq.q3": "Go — это то же самое, что и Zen?", "go.faq.a3": - "Нет. Zen - это оплата по мере использования, в то время как Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами и надежным доступом к моделям с открытым исходным кодом GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 и MiniMax M2.7.", + "Нет. Zen - это оплата по мере использования, в то время как Go начинается с $5 за первый месяц, затем $10/месяц, с щедрыми лимитами и надежным доступом к моделям с открытым исходным кодом GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7.", "go.faq.q4": "Сколько стоит Go?", "go.faq.a4.p1.beforePricing": "Go стоит", "go.faq.a4.p1.pricingLink": "$5 за первый месяц", @@ -355,7 +355,7 @@ export const dict = { "go.faq.q9": "В чем разница между бесплатными моделями и Go?", "go.faq.a9": - "Бесплатные модели включают Big Pickle плюс промо-модели, доступные на данный момент, с квотой 200 запросов/день. Go включает GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 и MiniMax M2.7 с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно $12 за 5 часов, $30 в неделю и $60 в месяц (фактическое количество запросов зависит от модели и использования).", + "Бесплатные модели включают Big Pickle плюс промо-модели, доступные на данный момент, с квотой 200 запросов/день. Go включает GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 и MiniMax M2.7 с более высокими квотами запросов, применяемыми в скользящих окнах (5 часов, неделя и месяц), что примерно эквивалентно $12 за 5 часов, $30 в неделю и $60 в месяц (фактическое количество запросов зависит от модели и использования).", "zen.api.error.rateLimitExceeded": "Превышен лимит запросов. Пожалуйста, попробуйте позже.", "zen.api.error.modelNotSupported": "Модель {{model}} не поддерживается", diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts index daf213eac7..59c90ef65d 100644 --- a/packages/console/app/src/i18n/th.ts +++ b/packages/console/app/src/i18n/th.ts @@ -250,7 +250,7 @@ export const dict = { "go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน", "go.meta.description": - "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 และ MiniMax M2.7", + "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดคำขอ 5 ชั่วโมงที่เอื้อเฟื้อสำหรับ GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7", "go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน", "go.hero.body": "Go นำการเขียนโค้ดแบบเอเจนต์มาสู่นักเขียนโปรแกรมทั่วโลก เสนอขีดจำกัดที่กว้างขวางและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดได้อย่างน่าเชื่อถือ เพื่อให้คุณสามารถสร้างสรรค์ด้วยเอเจนต์ที่ทรงพลังโดยไม่ต้องกังวลเรื่องค่าใช้จ่ายหรือความพร้อมใช้งาน", @@ -297,7 +297,8 @@ export const dict = { "go.problem.item1": "ราคาการสมัครสมาชิกที่ต่ำ", "go.problem.item2": "ขีดจำกัดที่กว้างขวางและการเข้าถึงที่เชื่อถือได้", "go.problem.item3": "สร้างขึ้นเพื่อโปรแกรมเมอร์จำนวนมากที่สุดเท่าที่จะเป็นไปได้", - "go.problem.item4": "รวมถึง GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 และ MiniMax M2.7", + "go.problem.item4": + "รวมถึง GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7", "go.how.title": "Go ทำงานอย่างไร", "go.how.body": "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน คุณสามารถใช้กับ OpenCode หรือเอเจนต์ใดก็ได้", "go.how.step1.title": "สร้างบัญชี", @@ -319,11 +320,10 @@ export const dict = { "go.faq.a1": "Go คือการสมัครสมาชิกราคาประหยัดที่ให้คุณเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสำหรับการเขียนโค้ดแบบเอเจนต์ได้อย่างน่าเชื่อถือ", "go.faq.q2": "Go รวมโมเดลอะไรบ้าง?", - "go.faq.a2": - "Go รวมถึง GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 และ MiniMax M2.7 พร้อมขีดจำกัดที่กว้างขวางและการเข้าถึงที่เชื่อถือได้", + "go.faq.a2": "Go รวมโมเดลด้านล่างนี้ พร้อมขีดจำกัดที่มากและการเข้าถึงที่เชื่อถือได้", "go.faq.q3": "Go เหมือนกับ Zen หรือไม่?", "go.faq.a3": - "ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ในขณะที่ Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์ส GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 และ MiniMax M2.7 อย่างเชื่อถือได้", + "ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ในขณะที่ Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์ส GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 อย่างเชื่อถือได้", "go.faq.q4": "Go ราคาเท่าไหร่?", "go.faq.a4.p1.beforePricing": "Go ราคา", "go.faq.a4.p1.pricingLink": "$5 เดือนแรก", @@ -346,7 +346,7 @@ export const dict = { "go.faq.q9": "ความแตกต่างระหว่างโมเดลฟรีและ Go คืออะไร?", "go.faq.a9": - "โมเดลฟรีรวมถึง Big Pickle บวกกับโมเดลโปรโมชั่นที่มีให้ในขณะนั้น ด้วยโควต้า 200 คำขอ/วัน Go รวมถึง GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 และ MiniMax M2.7 ที่มีโควต้าคำขอสูงกว่า ซึ่งบังคับใช้ผ่านช่วงเวลาหมุนเวียน (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน (จำนวนคำขอจริงจะแตกต่างกันไปตามโมเดลและการใช้งาน)", + "โมเดลฟรีรวมถึง Big Pickle บวกกับโมเดลโปรโมชั่นที่มีให้ในขณะนั้น ด้วยโควต้า 200 คำขอ/วัน Go รวมถึง GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 และ MiniMax M2.7 ที่มีโควต้าคำขอสูงกว่า ซึ่งบังคับใช้ผ่านช่วงเวลาหมุนเวียน (5 ชั่วโมง, รายสัปดาห์ และรายเดือน) เทียบเท่าประมาณ $12 ต่อ 5 ชั่วโมง, $30 ต่อสัปดาห์ และ $60 ต่อเดือน (จำนวนคำขอจริงจะแตกต่างกันไปตามโมเดลและการใช้งาน)", "zen.api.error.rateLimitExceeded": "เกินขีดจำกัดอัตราการใช้งาน กรุณาลองใหม่ในภายหลัง", "zen.api.error.modelNotSupported": "ไม่รองรับโมเดล {{model}}", diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts index 8eaafe207b..196bf9d376 100644 --- a/packages/console/app/src/i18n/tr.ts +++ b/packages/console/app/src/i18n/tr.ts @@ -253,7 +253,7 @@ export const dict = { "go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri", "go.meta.description": - "Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 ve MiniMax M2.7 için cömert 5 saatlik istek limitleri sunar.", + "Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 için cömert 5 saatlik istek limitleri sunar.", "go.hero.title": "Herkes için düşük maliyetli kodlama modelleri", "go.hero.body": "Go, dünya çapındaki programcılara ajan tabanlı kodlama getiriyor. En yetenekli açık kaynaklı modellere cömert limitler ve güvenilir erişim sunarak, maliyet veya erişilebilirlik konusunda endişelenmeden güçlü ajanlarla geliştirme yapmanızı sağlar.", @@ -302,7 +302,8 @@ export const dict = { "go.problem.item1": "Düşük maliyetli abonelik fiyatlandırması", "go.problem.item2": "Cömert limitler ve güvenilir erişim", "go.problem.item3": "Mümkün olduğunca çok programcı için geliştirildi", - "go.problem.item4": "GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 ve MiniMax M2.7 içerir", + "go.problem.item4": + "GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 içerir", "go.how.title": "Go nasıl çalışır?", "go.how.body": "Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar. OpenCode veya herhangi bir ajanla kullanabilirsiniz.", @@ -325,11 +326,10 @@ export const dict = { "go.faq.a1": "Go, ajan tabanlı kodlama için yetenekli açık kaynaklı modellere güvenilir erişim sağlayan düşük maliyetli bir aboneliktir.", "go.faq.q2": "Go hangi modelleri içerir?", - "go.faq.a2": - "Go, cömert limitler ve güvenilir erişim ile GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 ve MiniMax M2.7 modellerini içerir.", + "go.faq.a2": "Go, aşağıda listelenen modelleri cömert limitler ve güvenilir erişimle sunar.", "go.faq.q3": "Go, Zen ile aynı mı?", "go.faq.a3": - "Hayır. Zen kullandıkça öde modelidir, Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 ve MiniMax M2.7 açık kaynak modellerine cömert limitler ve güvenilir erişim sunar.", + "Hayır. Zen kullandıkça öde modelidir, Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 açık kaynak modellerine cömert limitler ve güvenilir erişim sunar.", "go.faq.q4": "Go ne kadar?", "go.faq.a4.p1.beforePricing": "Go'nun maliyeti", "go.faq.a4.p1.pricingLink": "İlk ay $5", @@ -353,7 +353,7 @@ export const dict = { "go.faq.q9": "Ücretsiz modeller ve Go arasındaki fark nedir?", "go.faq.a9": - "Ücretsiz modeller, günlük 200 istek kotası ile Big Pickle ve o sırada mevcut olan promosyonel modelleri içerir. Go ise GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 ve MiniMax M2.7 modellerini; yuvarlanan pencereler (5 saatlik, haftalık ve aylık) üzerinden uygulanan daha yüksek istek kotalarıyla içerir. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerine eşdeğerdir (gerçek istek sayıları modele ve kullanıma göre değişir).", + "Ücretsiz modeller, günlük 200 istek kotası ile Big Pickle ve o sırada mevcut olan promosyonel modelleri içerir. Go ise GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 ve MiniMax M2.7 modellerini; yuvarlanan pencereler (5 saatlik, haftalık ve aylık) üzerinden uygulanan daha yüksek istek kotalarıyla içerir. Bu kotalar kabaca her 5 saatte 12$, haftada 30$ ve ayda 60$ değerine eşdeğerdir (gerçek istek sayıları modele ve kullanıma göre değişir).", "zen.api.error.rateLimitExceeded": "İstek limiti aşıldı. Lütfen daha sonra tekrar deneyin.", "zen.api.error.modelNotSupported": "{{model}} modeli desteklenmiyor", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index bfbdd2702f..aaec74fbaa 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -241,7 +241,7 @@ export const dict = { "go.title": "OpenCode Go | 人人可用的低成本编程模型", "go.meta.description": - "Go 首月 $5,之后 $10/月,提供对 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5 和 MiniMax M2.7 的 5 小时充裕请求额度。", + "Go 首月 $5,之后 $10/月,提供对 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小时充裕请求额度。", "go.hero.title": "人人可用的低成本编程模型", "go.hero.body": "Go 将代理编程带给全世界的程序员。提供充裕的限额和对最强大的开源模型的可靠访问,让您可以利用强大的代理进行构建,而无需担心成本或可用性。", @@ -288,7 +288,8 @@ export const dict = { "go.problem.item1": "低成本订阅定价", "go.problem.item2": "充裕的限额和可靠的访问", "go.problem.item3": "为尽可能多的程序员打造", - "go.problem.item4": "包含 GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 和 MiniMax M2.7", + "go.problem.item4": + "包含 GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7", "go.how.title": "Go 如何工作", "go.how.body": "Go 起价为首月 $5,之后 $10/月。您可以将其与 OpenCode 或任何代理搭配使用。", "go.how.step1.title": "创建账户", @@ -307,11 +308,10 @@ export const dict = { "go.faq.q1": "什么是 OpenCode Go?", "go.faq.a1": "Go 是一项低成本订阅服务,为您提供对强大的开源模型的可靠访问,用于代理编程。", "go.faq.q2": "Go 包含哪些模型?", - "go.faq.a2": - "Go 包含 GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 和 MiniMax M2.7,并提供充裕的限额和可靠的访问。", + "go.faq.a2": "Go 包含下方列出的模型,提供充足的限额和可靠的访问。", "go.faq.q3": "Go 和 Zen 一样吗?", "go.faq.a3": - "不。Zen 是按量付费,而 Go 首月 $5,之后 $10/月,提供充裕的额度,并可可靠地访问 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5 和 MiniMax M2.7 等开源模型。", + "不。Zen 是按量付费,而 Go 首月 $5,之后 $10/月,提供充裕的额度,并可可靠地访问 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等开源模型。", "go.faq.q4": "Go 多少钱?", "go.faq.a4.p1.beforePricing": "Go 费用为", "go.faq.a4.p1.pricingLink": "首月 $5", @@ -333,7 +333,7 @@ export const dict = { "go.faq.q9": "免费模型和 Go 之间的区别是什么?", "go.faq.a9": - "免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 包含 GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, MiniMax M2.5 和 MiniMax M2.7,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60(实际请求计数因模型和使用情况而异)。", + "免费模型包含 Big Pickle 加上当时可用的促销模型,每天有 200 次请求的配额。Go 包含 GLM-5.1, GLM-5, Kimi K2.5, MiMo-V2-Pro, MiMo-V2-Omni, Qwen3.5 Plus, Qwen3.6 Plus, MiniMax M2.5 和 MiniMax M2.7,并在滚动窗口(5 小时、每周和每月)内执行更高的请求配额,大致相当于每 5 小时 $12、每周 $30 和每月 $60(实际请求计数因模型和使用情况而异)。", "zen.api.error.rateLimitExceeded": "超出速率限制。请稍后重试。", "zen.api.error.modelNotSupported": "不支持模型 {{model}}", diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts index 0a44f4fa04..b3e0fb0778 100644 --- a/packages/console/app/src/i18n/zht.ts +++ b/packages/console/app/src/i18n/zht.ts @@ -241,7 +241,7 @@ export const dict = { "go.title": "OpenCode Go | 低成本全民編碼模型", "go.meta.description": - "Go 首月 $5,之後 $10/月,提供對 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5 和 MiniMax M2.7 的 5 小時充裕請求額度。", + "Go 首月 $5,之後 $10/月,提供對 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 的 5 小時充裕請求額度。", "go.hero.title": "低成本全民編碼模型", "go.hero.body": "Go 將代理編碼帶給全世界的程式設計師。提供寬裕的限額以及對最強大開源模型的穩定存取,讓你可以使用強大的代理進行構建,而無需擔心成本或可用性。", @@ -288,7 +288,8 @@ export const dict = { "go.problem.item1": "低成本訂閱定價", "go.problem.item2": "寬裕的限額與穩定存取", "go.problem.item3": "專為盡可能多的程式設計師打造", - "go.problem.item4": "包含 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5 與 MiniMax M2.7", + "go.problem.item4": + "包含 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7", "go.how.title": "Go 如何運作", "go.how.body": "Go 起價為首月 $5,之後 $10/月。您可以將其與 OpenCode 或任何代理搭配使用。", "go.how.step1.title": "建立帳號", @@ -307,11 +308,10 @@ export const dict = { "go.faq.q1": "什麼是 OpenCode Go?", "go.faq.a1": "Go 是一個低成本訂閱方案,讓你穩定存取強大的開源模型以進行代理編碼。", "go.faq.q2": "Go 包含哪些模型?", - "go.faq.a2": - "Go 包含 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5 與 MiniMax M2.7,並提供寬裕的限額與穩定存取。", + "go.faq.a2": "Go 包含下方列出的模型,提供充足的額度與穩定的存取。", "go.faq.q3": "Go 與 Zen 一樣嗎?", "go.faq.a3": - "不。Zen 是按量付費,而 Go 首月 $5,之後 $10/月,提供充裕的額度,並可可靠地存取 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5 和 MiniMax M2.7 等開源模型。", + "不。Zen 是按量付費,而 Go 首月 $5,之後 $10/月,提供充裕的額度,並可可靠地存取 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 和 MiniMax M2.7 等開源模型。", "go.faq.q4": "Go 費用是多少?", "go.faq.a4.p1.beforePricing": "Go 費用為", "go.faq.a4.p1.pricingLink": "首月 $5", @@ -333,7 +333,7 @@ export const dict = { "go.faq.q9": "免費模型與 Go 有什麼區別?", "go.faq.a9": - "免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為 200 次請求/天。Go 包括 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、MiniMax M2.5 與 MiniMax M2.7,並在滾動視窗(5 小時、每週和每月)內執行更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60(實際請求數因模型和使用情況而異)。", + "免費模型包括 Big Pickle 以及當時可用的促銷模型,配額為 200 次請求/天。Go 包括 GLM-5.1、GLM-5、Kimi K2.5、MiMo-V2-Pro、MiMo-V2-Omni、Qwen3.5 Plus、Qwen3.6 Plus、MiniMax M2.5 與 MiniMax M2.7,並在滾動視窗(5 小時、每週和每月)內執行更高的請求配額,大約相當於每 5 小時 $12、每週 $30 和每月 $60(實際請求數因模型和使用情況而異)。", "zen.api.error.rateLimitExceeded": "超出頻率限制。請稍後再試。", "zen.api.error.modelNotSupported": "不支援模型 {{model}}", diff --git a/packages/console/app/src/routes/download/[channel]/[platform].ts b/packages/console/app/src/routes/download/[channel]/[platform].ts index 3321b1999e..e9b3f23e79 100644 --- a/packages/console/app/src/routes/download/[channel]/[platform].ts +++ b/packages/console/app/src/routes/download/[channel]/[platform].ts @@ -22,7 +22,7 @@ export async function GET({ params: { platform, channel } }: APIEvent) { if (!assetName) return new Response(null, { status: 404 }) const resp = await fetch( - `https://github.com/anomalyco/${channel === "stable" ? "opencode" : "opencode-beta"}/releases/download/v1.4.3/${assetName}`, + `https://github.com/anomalyco/${channel === "stable" ? "opencode" : "opencode-beta"}/releases/latest/download/${assetName}`, { cf: { // in case gh releases has rate limits diff --git a/packages/console/app/src/routes/go/index.css b/packages/console/app/src/routes/go/index.css index 68f27a1bb8..cd4406f253 100644 --- a/packages/console/app/src/routes/go/index.css +++ b/packages/console/app/src/routes/go/index.css @@ -1017,6 +1017,30 @@ body { [data-slot="faq-answer"] { margin-left: 40px; margin-bottom: 32px; + + [data-slot="faq-models"] { + margin-top: 16px; + overflow-x: auto; + + table { + width: 100%; + min-width: 28rem; + border-collapse: collapse; + } + + th, + td { + padding: 12px 16px; + text-align: left; + white-space: nowrap; + } + + th { + background: var(--color-background-weak); + color: var(--color-text-strong); + font-weight: 500; + } + } } } diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index e4628fe110..0ac85a9570 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -12,7 +12,7 @@ import { Footer } from "~/component/footer" import { Header } from "~/component/header" import { config } from "~/config" import { getLastSeenWorkspaceID } from "../workspace/common" -import { IconMiniMax, IconMiMo, IconZai } from "~/component/icon" +import { IconMiniMax, IconMiMo, IconZai, IconAlibaba } from "~/component/icon" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" import { LocaleLinks } from "~/component/locale-links" @@ -22,6 +22,18 @@ const checkLoggedIn = query(async () => { return await getLastSeenWorkspaceID().catch(() => undefined) }, "checkLoggedIn.get") +const models = [ + { name: "GLM-5.1", provider: "DeepInfra, Z.ai" }, + { name: "GLM-5", provider: "DeepInfra, Z.ai" }, + { name: "Kimi K2.5", provider: "Moonshot AI" }, + { name: "MiMo-V2-Pro", provider: "Xiaomi MiMo" }, + { name: "MiMo-V2-Omni", provider: "Xiaomi MiMo" }, + { name: "Qwen3.5 Plus", provider: "Alibaba Cloud Model Studio" }, + { name: "Qwen3.6 Plus", provider: "Alibaba Cloud Model Studio" }, + { name: "MiniMax M2.7", provider: "MiniMax" }, + { name: "MiniMax M2.5", provider: "MiniMax" }, +] + function LimitsGraph(props: { href: string }) { let root!: HTMLElement const [visible, setVisible] = createSignal(false) @@ -44,13 +56,13 @@ function LimitsGraph(props: { href: string }) { }) const free = 200 - const models = [ + const graph = [ { id: "glm-5.1", name: "GLM-5.1", req: 880, d: "100ms" }, - { id: "glm-5", name: "GLM-5", req: 1150, d: "120ms" }, { id: "mimo-v2-pro", name: "MiMo-V2-Pro", req: 1290, d: "150ms" }, { id: "kimi", name: "Kimi K2.5", req: 1850, d: "240ms" }, - { id: "minimax-m2.7", name: "MiniMax M2.7", req: 14000, d: "330ms" }, - { id: "minimax-m2.5", name: "MiniMax M2.5", req: 20000, d: "360ms" }, + { id: "qwen3.6-plus", name: "Qwen3.6 Plus", req: 3300, d: "280ms" }, + { id: "minimax-m2.7", name: "MiniMax M2.7", req: 3400, d: "300ms" }, + { id: "qwen3.5-plus", name: "Qwen3.5 Plus", req: 10200, d: "360ms" }, ] const w = 720 @@ -62,7 +74,7 @@ function LimitsGraph(props: { href: string }) { const plot = w - left - right const ratio = (n: number) => n / free - const rmax = Math.max(1, ...models.map((m) => ratio(m.req))) + const rmax = Math.max(1, ...graph.map((m) => ratio(m.req))) const log = (n: number) => Math.log10(Math.max(n, 1)) const base = 24 const p = 1.8 @@ -93,7 +105,7 @@ function LimitsGraph(props: { href: string }) { const sep = bh + 40 const fy = top + 22 const gy = (i: number) => fy + sep + step * i - const my = models.length < 2 ? gy(0) : (gy(0) + gy(models.length - 1)) / 2 + const my = graph.length < 2 ? gy(0) : (gy(0) + gy(graph.length - 1)) / 2 const px = (n: number) => `${(n / w) * 100}%` const py = (n: number) => `${(n / h) * 100}%` const lx = px(left - 16) @@ -132,7 +144,7 @@ function LimitsGraph(props: { href: string }) { - + {(m, i) => ( {free.toLocaleString()} {i18n.t("go.graph.freePill")} - + {(m, i) => ( +
+ +
@@ -420,7 +435,31 @@ export default function Home() { {i18n.t("go.faq.a1")}
  • - {i18n.t("go.faq.a2")} + + {i18n.t("go.faq.a2")} + {language.locale() === "en" && ( +
    + + + + + + + + + + {(m) => ( + + + + + )} + + +
    {i18n.t("workspace.models.table.model")}{i18n.t("workspace.providers.table.provider")}
    {m.name}{m.provider}
    +
    + )} +
  • {i18n.t("go.faq.a9")} diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx index 70d6aea7a2..95ff7af2b9 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx @@ -292,6 +292,8 @@ export function LiteSection() {
  • Mimo-V2-Omni
  • MiniMax M2.5
  • MiniMax M2.7
  • +
  • Qwen3.5 Plus
  • +
  • Qwen3.6 Plus
  • {i18n.t("workspace.lite.promo.footer")}

    diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 46d8435225..58df618094 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -222,22 +222,23 @@ export async function handler( logger.debug("STATUS: " + res.status + " " + res.statusText) // Handle non-streaming response - if (!isStream) { + if (!isStream || res.status === 429) { const json = await res.json() - const usageInfo = providerInfo.normalizeUsage(json.usage) - const costInfo = calculateCost(modelInfo, usageInfo) - await trialLimiter?.track(usageInfo) await rateLimiter?.track() - await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo) - await reload(billingSource, authInfo, costInfo) + if (json.usage) { + const usageInfo = providerInfo.normalizeUsage(json.usage) + const costInfo = calculateCost(modelInfo, usageInfo) + await trialLimiter?.track(usageInfo) + await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo) + await reload(billingSource, authInfo, costInfo) + json.cost = calculateOccurredCost(billingSource, costInfo) + } + if (json.error?.message) { + json.error.message = `Error from provider${providerInfo.displayName ? ` (${providerInfo.displayName})` : ""}: ${json.error.message}` + } const responseConverter = createResponseConverter(providerInfo.format, opts.format) - const body = JSON.stringify( - responseConverter({ - ...json, - cost: calculateOccurredCost(billingSource, costInfo), - }), - ) + const body = JSON.stringify(responseConverter(json)) logger.metric({ response_length: body.length }) logger.debug("RESPONSE: " + body) dataDumper?.provideResponse(body) diff --git a/packages/console/core/package.json b/packages/console/core/package.json index b69aa3adda..760174cf04 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.4.4", + "version": "1.4.6", "private": true, "type": "module", "license": "MIT", diff --git a/packages/console/core/src/model.ts b/packages/console/core/src/model.ts index b4149373fe..3d614d3034 100644 --- a/packages/console/core/src/model.ts +++ b/packages/console/core/src/model.ts @@ -44,6 +44,7 @@ export namespace ZenData { }) const ProviderSchema = z.object({ + displayName: z.string().optional(), api: z.string(), apiKey: z.union([z.string(), z.record(z.string(), z.string())]), format: FormatSchema.optional(), diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 18729e6047..840f32742d 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.4.4", + "version": "1.4.6", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index eccb128acf..5aa94224e7 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.4.4", + "version": "1.4.6", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index bcefa0e1f9..cc0bad7d74 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.4.4", + "version": "1.4.6", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop-electron/src/main/store.ts b/packages/desktop-electron/src/main/store.ts index cf2d25b110..709e820e25 100644 --- a/packages/desktop-electron/src/main/store.ts +++ b/packages/desktop-electron/src/main/store.ts @@ -7,7 +7,7 @@ const cache = new Map() export function getStore(name = SETTINGS_STORE) { const cached = cache.get(name) if (cached) return cached - const next = new Store({ name, fileExtension: "" }) + const next = new Store({ name, fileExtension: "", accessPropertiesByDotNotation: false }) cache.set(name, next) return next } diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 78ecccd236..3fd02ad881 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.4.4", + "version": "1.4.6", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 65c2e947be..3c4a835f35 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.4.4", + "version": "1.4.6", "private": true, "type": "module", "license": "MIT", @@ -13,7 +13,7 @@ "shell-prod": "sst shell --target Teams --stage production" }, "dependencies": { - "@opencode-ai/util": "workspace:*", + "@opencode-ai/shared": "workspace:*", "@opencode-ai/ui": "workspace:*", "aws4fetch": "^1.0.20", "@pierre/diffs": "catalog:", diff --git a/packages/enterprise/src/core/share.ts b/packages/enterprise/src/core/share.ts index 18fcd7a071..1a343272f7 100644 --- a/packages/enterprise/src/core/share.ts +++ b/packages/enterprise/src/core/share.ts @@ -1,6 +1,6 @@ import { Message, Model, Part, Session, SnapshotFileDiff } from "@opencode-ai/sdk/v2" -import { fn } from "@opencode-ai/util/fn" -import { iife } from "@opencode-ai/util/iife" +import { fn } from "@opencode-ai/shared/util/fn" +import { iife } from "@opencode-ai/shared/util/iife" import z from "zod" import { Storage } from "./storage" diff --git a/packages/enterprise/src/core/storage.ts b/packages/enterprise/src/core/storage.ts index b8030b4f90..a6222e4154 100644 --- a/packages/enterprise/src/core/storage.ts +++ b/packages/enterprise/src/core/storage.ts @@ -1,5 +1,5 @@ import { AwsClient } from "aws4fetch" -import { lazy } from "@opencode-ai/util/lazy" +import { lazy } from "@opencode-ai/shared/util/lazy" export namespace Storage { export interface Adapter { diff --git a/packages/enterprise/src/routes/share/[shareID].tsx b/packages/enterprise/src/routes/share/[shareID].tsx index edeeaf1ad5..f3be14e393 100644 --- a/packages/enterprise/src/routes/share/[shareID].tsx +++ b/packages/enterprise/src/routes/share/[shareID].tsx @@ -10,9 +10,9 @@ import { Share } from "~/core/share" import { Logo, Mark } from "@opencode-ai/ui/logo" import { IconButton } from "@opencode-ai/ui/icon-button" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" -import { iife } from "@opencode-ai/util/iife" -import { Binary } from "@opencode-ai/util/binary" -import { NamedError } from "@opencode-ai/util/error" +import { iife } from "@opencode-ai/shared/util/iife" +import { Binary } from "@opencode-ai/shared/util/binary" +import { NamedError } from "@opencode-ai/shared/util/error" import { DateTime } from "luxon" import { createStore } from "solid-js/store" import z from "zod" diff --git a/packages/enterprise/test/core/share.test.ts b/packages/enterprise/test/core/share.test.ts index d49d4b7639..34f3b17a3f 100644 --- a/packages/enterprise/test/core/share.test.ts +++ b/packages/enterprise/test/core/share.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test, afterAll } from "bun:test" import { Share } from "../../src/core/share" import { Storage } from "../../src/core/storage" -import { Identifier } from "@opencode-ai/util/identifier" +import { Identifier } from "@opencode-ai/shared/util/identifier" describe.concurrent("core.share", () => { test("should create a share", async () => { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 5ad0475013..d6be9c6b61 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.4.4" +version = "1.4.6" 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.4.4/opencode-darwin-arm64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.6/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.4/opencode-darwin-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.6/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.4/opencode-linux-arm64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.6/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.4.4/opencode-linux-x64.tar.gz" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.6/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.4.4/opencode-windows-x64.zip" +archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.6/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 33023ff134..941f093fcc 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.4.4", + "version": "1.4.6", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index f875a0f2a8..59be93d620 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.4.4", + "version": "1.4.6", "name": "opencode", "type": "module", "license": "MIT", @@ -44,6 +44,7 @@ "@effect/language-service": "0.84.2", "@octokit/webhooks-types": "7.6.1", "@opencode-ai/script": "workspace:*", + "@opencode-ai/shared": "workspace:*", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", @@ -112,14 +113,15 @@ "@octokit/rest": "catalog:", "@openauthjs/openauth": "catalog:", "@opencode-ai/plugin": "workspace:*", - "@opencode-ai/server": "workspace:*", "@opencode-ai/script": "workspace:*", "@opencode-ai/sdk": "workspace:*", - "@opencode-ai/util": "workspace:*", + "@opencode-ai/server": "workspace:*", + "@openrouter/ai-sdk-provider": "2.5.1", + "@opentelemetry/api": "1.9.0", + "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", - "@openrouter/ai-sdk-provider": "2.5.1", "@opentui/core": "0.1.99", "@opentui/solid": "0.1.99", "@parcel/watcher": "2.5.1", diff --git a/packages/opencode/specs/effect/instance-context.md b/packages/opencode/specs/effect/instance-context.md new file mode 100644 index 0000000000..6c160a9477 --- /dev/null +++ b/packages/opencode/specs/effect/instance-context.md @@ -0,0 +1,310 @@ +# Instance context migration + +Practical plan for retiring the promise-backed / ALS-backed `Instance` helper in `src/project/instance.ts` and moving instance selection fully into Effect-provided scope. + +## Goal + +End state: + +- request, CLI, TUI, and tool entrypoints shift into an instance through Effect, not `Instance.provide(...)` +- Effect code reads the current instance from `InstanceRef` or its eventual replacement, not from ALS-backed sync getters +- per-directory boot, caching, and disposal are scoped Effect resources, not a module-level `Map>` +- ALS remains only as a temporary bridge for native callback APIs that fire outside the Effect fiber tree + +## Current split + +Today `src/project/instance.ts` still owns two separate concerns: + +- ambient current-instance context through `LocalContext` / `AsyncLocalStorage` +- per-directory boot and deduplication through `cache: Map>` + +At the same time, the Effect side already exists: + +- `src/effect/instance-ref.ts` provides `InstanceRef` and `WorkspaceRef` +- `src/effect/run-service.ts` already attaches those refs when a runtime starts inside an active instance ALS context +- `src/effect/instance-state.ts` already prefers `InstanceRef` and only falls back to ALS when needed + +That means the migration is not "invent instance context in Effect". The migration is "stop relying on the legacy helper as the primary source of truth". + +## End state shape + +Near-term target shape: + +```ts +InstanceScope.with({ directory, workspaceID }, effect) +``` + +Responsibilities of `InstanceScope.with(...)`: + +- resolve `directory`, `project`, and `worktree` +- acquire or reuse the scoped per-directory instance environment +- provide `InstanceRef` and `WorkspaceRef` +- run the caller's Effect inside that environment + +Code inside the boundary should then do one of these: + +```ts +const ctx = yield * InstanceState.context +const dir = yield * InstanceState.directory +``` + +Long-term, once `InstanceState` itself is replaced by keyed layers / `LayerMap`, those reads can move to an `InstanceContext` service without changing the outer migration order. + +## Migration phases + +### Phase 1: stop expanding the legacy surface + +Rules for all new code: + +- do not add new `Instance.directory`, `Instance.worktree`, `Instance.project`, or `Instance.current` reads inside Effect code +- do not add new `Instance.provide(...)` boundaries unless there is no Effect-native seam yet +- use `InstanceState.context`, `InstanceState.directory`, or an explicit `ctx` parameter inside Effect code + +Success condition: + +- the file inventory below only shrinks from here + +### Phase 2: remove direct sync getter reads from Effect services + +Convert Effect services first, before replacing the top-level boundary. These modules already run inside Effect and mostly need `yield* InstanceState.context` or a yielded `ctx` instead of ambient sync access. + +Primary batch, highest payoff: + +- `src/file/index.ts` +- `src/lsp/server.ts` +- `src/worktree/index.ts` +- `src/file/watcher.ts` +- `src/format/formatter.ts` +- `src/session/index.ts` +- `src/project/vcs.ts` + +Mechanical replacement rule: + +- `Instance.directory` -> `ctx.directory` or `yield* InstanceState.directory` +- `Instance.worktree` -> `ctx.worktree` +- `Instance.project` -> `ctx.project` + +Do not thread strings manually through every public method if the service already has access to Effect context. + +### Phase 3: convert entry boundaries to provide instance refs directly + +After the service bodies stop assuming ALS, move the top-level boundaries to shift into Effect explicitly. + +Main boundaries: + +- HTTP server middleware and experimental `HttpApi` entrypoints +- CLI commands +- TUI worker / attach / thread entrypoints +- tool execution entrypoints + +These boundaries should become Effect-native wrappers that: + +- decode directory / workspace inputs +- resolve the instance context once +- provide `InstanceRef` and `WorkspaceRef` +- run the requested Effect + +At that point `Instance.provide(...)` becomes a legacy adapter instead of the normal code path. + +### Phase 4: replace promise boot cache with scoped instance runtime + +Once boundaries and services both rely on Effect context, replace the module-level promise cache in `src/project/instance.ts`. + +Target replacement: + +- keyed scoped runtime or keyed layer acquisition for each directory +- reuse via `ScopedCache`, `LayerMap`, or another keyed Effect resource manager +- cleanup performed by scope finalizers instead of `disposeAll()` iterating a Promise map + +This phase should absorb the current responsibilities of: + +- `cache` in `src/project/instance.ts` +- `boot(...)` +- most of `disposeInstance(...)` +- manual `reload(...)` / `disposeAll()` fan-out logic + +### Phase 5: shrink ALS to callback bridges only + +Keep ALS only where a library invokes callbacks outside the Effect fiber tree and we still need to call code that reads instance context synchronously. + +Known bridge cases today: + +- `src/file/watcher.ts` +- `src/session/llm.ts` +- some LSP and plugin callback paths + +If those libraries become fully wrapped in Effect services, the remaining `Instance.bind(...)` uses can disappear too. + +### Phase 6: delete the legacy sync API + +Only after earlier phases land: + +- remove broad use of `Instance.current`, `Instance.directory`, `Instance.worktree`, `Instance.project` +- reduce `src/project/instance.ts` to a thin compatibility shim or delete it entirely +- remove the ALS fallback from `InstanceState.context` + +## Inventory of direct legacy usage + +Direct legacy usage means any source file that still calls one of: + +- `Instance.current` +- `Instance.directory` +- `Instance.worktree` +- `Instance.project` +- `Instance.provide(...)` +- `Instance.bind(...)` +- `Instance.restore(...)` +- `Instance.reload(...)` +- `Instance.dispose()` / `Instance.disposeAll()` + +Current total: `54` files in `packages/opencode/src`. + +### Core bridge and plumbing + +These files define or adapt the current bridge. They should change last, after callers have moved. + +- `src/project/instance.ts` +- `src/effect/run-service.ts` +- `src/effect/instance-state.ts` +- `src/project/bootstrap.ts` +- `src/config/config.ts` + +Migration rule: + +- keep these as compatibility glue until the outer boundaries and inner services stop depending on ALS + +### HTTP and server boundaries + +These are the current request-entry seams that still create or consume instance context through the legacy helper. + +- `src/server/instance/middleware.ts` +- `src/server/instance/index.ts` +- `src/server/instance/project.ts` +- `src/server/instance/workspace.ts` +- `src/server/instance/file.ts` +- `src/server/instance/experimental.ts` +- `src/server/instance/global.ts` + +Migration rule: + +- move these to explicit Effect entrypoints that provide `InstanceRef` / `WorkspaceRef` +- do not move these first; first reduce the number of downstream handlers and services that still expect ambient ALS + +### CLI and TUI boundaries + +These commands still enter an instance through `Instance.provide(...)` or read sync getters directly. + +- `src/cli/bootstrap.ts` +- `src/cli/cmd/agent.ts` +- `src/cli/cmd/debug/agent.ts` +- `src/cli/cmd/debug/ripgrep.ts` +- `src/cli/cmd/github.ts` +- `src/cli/cmd/import.ts` +- `src/cli/cmd/mcp.ts` +- `src/cli/cmd/models.ts` +- `src/cli/cmd/plug.ts` +- `src/cli/cmd/pr.ts` +- `src/cli/cmd/providers.ts` +- `src/cli/cmd/stats.ts` +- `src/cli/cmd/tui/attach.ts` +- `src/cli/cmd/tui/plugin/runtime.ts` +- `src/cli/cmd/tui/thread.ts` +- `src/cli/cmd/tui/worker.ts` + +Migration rule: + +- converge these on one shared `withInstance(...)` Effect entry helper instead of open-coded `Instance.provide(...)` +- after that helper is proven, inline the legacy implementation behind an Effect-native scope provider + +### Tool boundary code + +These tools mostly use direct getters for path resolution and repo-relative display logic. + +- `src/tool/apply_patch.ts` +- `src/tool/bash.ts` +- `src/tool/edit.ts` +- `src/tool/lsp.ts` +- `src/tool/multiedit.ts` +- `src/tool/plan.ts` +- `src/tool/read.ts` +- `src/tool/write.ts` + +Migration rule: + +- expose the current instance as an explicit Effect dependency for tool execution +- keep path logic local; avoid introducing another global singleton for tool state + +### Effect services still reading ambient instance state + +These modules are already the best near-term migration targets because they are in Effect code but still read sync getters from the legacy helper. + +- `src/agent/agent.ts` +- `src/config/tui-migrate.ts` +- `src/file/index.ts` +- `src/file/watcher.ts` +- `src/format/formatter.ts` +- `src/lsp/client.ts` +- `src/lsp/index.ts` +- `src/lsp/server.ts` +- `src/mcp/index.ts` +- `src/project/vcs.ts` +- `src/provider/provider.ts` +- `src/pty/index.ts` +- `src/session/index.ts` +- `src/session/instruction.ts` +- `src/session/llm.ts` +- `src/session/system.ts` +- `src/sync/index.ts` +- `src/worktree/index.ts` + +Migration rule: + +- replace direct getter reads with `yield* InstanceState.context` or a yielded `ctx` +- isolate `Instance.bind(...)` callers and convert only the truly callback-driven edges to bridge mode + +### Highest-churn hotspots + +Current highest direct-usage counts by file: + +- `src/file/index.ts` - `18` +- `src/lsp/server.ts` - `14` +- `src/worktree/index.ts` - `12` +- `src/file/watcher.ts` - `9` +- `src/cli/cmd/mcp.ts` - `8` +- `src/format/formatter.ts` - `8` +- `src/tool/apply_patch.ts` - `8` +- `src/cli/cmd/github.ts` - `7` + +These files should drive the first measurable burn-down. + +## Recommended implementation order + +1. Migrate direct getter reads inside Effect services, starting with `file`, `lsp`, `worktree`, `format`, and `session`. +2. Add one shared Effect-native boundary helper for CLI / tool / HTTP entrypoints so we stop open-coding `Instance.provide(...)`. +3. Move experimental `HttpApi` entrypoints to that helper so the new server stack proves the pattern. +4. Convert remaining CLI and tool boundaries. +5. Replace the promise cache with a keyed scoped runtime or keyed layer map. +6. Delete ALS fallback paths once only callback bridges still depend on them. + +## Definition of done + +This migration is done when all of the following are true: + +- new requests and commands enter an instance by providing Effect context, not ALS +- Effect services no longer read `Instance.directory`, `Instance.worktree`, `Instance.project`, or `Instance.current` +- `Instance.provide(...)` is gone from normal request / CLI / tool execution +- per-directory boot and disposal are handled by scoped Effect resources +- `Instance.bind(...)` is either gone or confined to a tiny set of native callback adapters + +## Tracker and worktree + +Active tracker items: + +- `lh7l73` - overall `HttpApi` migration +- `yobwlk` - remove direct `Instance.*` reads inside Effect services +- `7irl1e` - replace `InstanceState` / legacy instance caching with keyed Effect layers + +Dedicated worktree for this transition: + +- path: `/Users/kit/code/open-source/opencode-worktrees/instance-effect-shift` +- branch: `kit/instance-effect-shift` diff --git a/packages/opencode/specs/effect/migration.md b/packages/opencode/specs/effect/migration.md index b8d4d12597..105a82290b 100644 --- a/packages/opencode/specs/effect/migration.md +++ b/packages/opencode/specs/effect/migration.md @@ -13,6 +13,10 @@ Use `makeRuntime` (from `src/effect/run-service.ts`) to create a per-service `Ma Rule of thumb: if two open directories should not share one copy of the service, it needs `InstanceState`. +## Instance context transition + +See `instance-context.md` for the phased plan to remove the legacy ALS / promise-backed `Instance` helper and move request / CLI / tool boundaries onto Effect-provided instance scope. + ## Service shape Every service follows the same pattern — a single namespace with the service definition, layer, `runPromise`, and async facade functions: diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 09f8663ed0..8ac09e4bb3 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -34,7 +34,7 @@ import { import { Log } from "../util/log" import { pathToFileURL } from "url" import { Filesystem } from "../util/filesystem" -import { Hash } from "../util/hash" +import { Hash } from "@opencode-ai/shared/util/hash" import { ACPSessionManager } from "./session" import type { ACPConfig } from "./types" import { Provider } from "../provider/provider" diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index b1502da78c..b287ce551e 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -2,7 +2,7 @@ import path from "path" import { Effect, Layer, Record, Result, Schema, Context } from "effect" import { zod } from "@/util/effect-zod" import { Global } from "../global" -import { AppFileSystem } from "../filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key" diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index 0638777bd4..3a1eea5c73 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -1,6 +1,6 @@ import z from "zod" import { Effect, Exit, Layer, PubSub, Scope, Context, Stream } from "effect" -import { EffectLogger } from "@/effect/logger" +import { EffectBridge } from "@/effect/bridge" import { Log } from "../util/log" import { BusEvent } from "./bus-event" import { GlobalBus } from "./global" @@ -128,6 +128,7 @@ export namespace Bus { function on(pubsub: PubSub.PubSub, type: string, callback: (event: T) => unknown) { return Effect.gen(function* () { log.info("subscribing", { type }) + const bridge = yield* EffectBridge.make() const scope = yield* Scope.make() const subscription = yield* Scope.provide(scope)(PubSub.subscribe(pubsub)) @@ -147,7 +148,7 @@ export namespace Bus { return () => { log.info("unsubscribing", { type }) - Effect.runFork(Scope.close(scope, Exit.void).pipe(Effect.provide(EffectLogger.layer))) + bridge.fork(Scope.close(scope, Exit.void)) } }) } diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index 498db99a1b..cab162f8f0 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -22,7 +22,7 @@ import { createStore, produce, reconcile } from "solid-js/store" import { useProject } from "@tui/context/project" import { useEvent } from "@tui/context/event" import { useSDK } from "@tui/context/sdk" -import { Binary } from "@opencode-ai/util/binary" +import { Binary } from "@opencode-ai/shared/util/binary" import { createSimpleContext } from "./helper" import type { Snapshot } from "@/snapshot" import { useExit } from "./exit" @@ -336,7 +336,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ case "lsp.updated": { const workspace = project.workspace.current() - sdk.client.lsp.status({ workspace }).then((x) => setStore("lsp", x.data!)) + sdk.client.lsp.status({ workspace }).then((x) => setStore("lsp", x.data ?? [])) break } @@ -419,14 +419,14 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ ...(args.continue ? [] : [sessionListPromise.then((sessions) => setStore("session", reconcile(sessions)))]), consoleStatePromise.then((consoleState) => setStore("console_state", reconcile(consoleState))), sdk.client.command.list({ workspace }).then((x) => setStore("command", reconcile(x.data ?? []))), - sdk.client.lsp.status({ workspace }).then((x) => setStore("lsp", reconcile(x.data!))), - sdk.client.mcp.status({ workspace }).then((x) => setStore("mcp", reconcile(x.data!))), + sdk.client.lsp.status({ workspace }).then((x) => setStore("lsp", reconcile(x.data ?? []))), + sdk.client.mcp.status({ workspace }).then((x) => setStore("mcp", reconcile(x.data ?? {}))), sdk.client.experimental.resource .list({ workspace }) .then((x) => setStore("mcp_resource", reconcile(x.data ?? {}))), - sdk.client.formatter.status({ workspace }).then((x) => setStore("formatter", reconcile(x.data!))), + sdk.client.formatter.status({ workspace }).then((x) => setStore("formatter", reconcile(x.data ?? []))), sdk.client.session.status({ workspace }).then((x) => { - setStore("session_status", reconcile(x.data!)) + setStore("session_status", reconcile(x.data ?? {})) }), sdk.client.provider.auth({ workspace }).then((x) => setStore("provider_auth", reconcile(x.data ?? {}))), sdk.client.vcs.get({ workspace }).then((x) => setStore("vcs", reconcile(x.data))), diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx index de81529961..0c0658e743 100644 --- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx @@ -2,7 +2,7 @@ import { CliRenderEvents, SyntaxStyle, RGBA, type TerminalColors } from "@opentu import path from "path" import { createEffect, createMemo, onCleanup, onMount } from "solid-js" import { createSimpleContext } from "./helper" -import { Glob } from "../../../../util/glob" +import { Glob } from "@opencode-ai/shared/util/glob" import aura from "./theme/aura.json" with { type: "json" } import ayu from "./theme/ayu.json" with { type: "json" } import catppuccin from "./theme/catppuccin.json" with { type: "json" } diff --git a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts index 2f7fd51643..7f12106b2c 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/runtime.ts @@ -34,7 +34,7 @@ import { hasTheme, upsertTheme } from "../context/theme" import { Global } from "@/global" import { Filesystem } from "@/util/filesystem" import { Process } from "@/util/process" -import { Flock } from "@/util/flock" +import { Flock } from "@opencode-ai/shared/util/flock" import { Flag } from "@/flag/flag" import { INTERNAL_TUI_PLUGINS, type InternalTuiPlugin } from "./internal" import { setupSlots, Slot as View } from "./slots" diff --git a/packages/opencode/src/cli/ui.ts b/packages/opencode/src/cli/ui.ts index b24a2a2f44..d735a55417 100644 --- a/packages/opencode/src/cli/ui.ts +++ b/packages/opencode/src/cli/ui.ts @@ -1,6 +1,6 @@ import z from "zod" import { EOL } from "os" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { logo as glyphs } from "./logo" export namespace UI { diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 42f53301b2..91a9e1b405 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -1,9 +1,9 @@ import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect/instance-state" +import { EffectBridge } from "@/effect/bridge" import type { InstanceContext } from "@/project/instance" import { SessionID, MessageID } from "@/session/schema" import { Effect, Layer, Context } from "effect" -import { EffectLogger } from "@/effect/logger" import z from "zod" import { Config } from "../config/config" import { MCP } from "../mcp" @@ -82,6 +82,7 @@ export namespace Command { const init = Effect.fn("Command.state")(function* (ctx: InstanceContext) { const cfg = yield* config.get() + const bridge = yield* EffectBridge.make() const commands: Record = {} commands[Default.INIT] = { @@ -125,7 +126,7 @@ export namespace Command { source: "mcp", description: prompt.description, get template() { - return Effect.runPromise( + return bridge.promise( mcp .getPrompt( prompt.client, @@ -141,7 +142,6 @@ export namespace Command { .map((message) => (message.content.type === "text" ? message.content.text : "")) .join("\n") || "", ), - Effect.provide(EffectLogger.layer), ), ) }, diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index f9ca883414..915e604e90 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -7,7 +7,7 @@ import z from "zod" import { mergeDeep, pipe, unique } from "remeda" import { Global } from "../global" import fsNode from "fs/promises" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { Flag } from "../flag/flag" import { Auth } from "../auth" import { Env } from "../env" @@ -26,17 +26,17 @@ import { existsSync } from "fs" import { Bus } from "@/bus" import { GlobalBus } from "@/bus/global" import { Event } from "../server/event" -import { Glob } from "../util/glob" +import { Glob } from "@opencode-ai/shared/util/glob" import { Account } from "@/account" import { isRecord } from "@/util/record" import { ConfigPaths } from "./paths" import type { ConsoleState } from "./console-state" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { InstanceState } from "@/effect/instance-state" import { Context, Duration, Effect, Exit, Fiber, Layer, Option } from "effect" -import { Flock } from "@/util/flock" +import { Flock } from "@opencode-ai/shared/util/flock" import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared" -import { Npm } from "@/npm" +import { Npm } from "../npm" import { InstanceRef } from "@/effect/instance-ref" export namespace Config { diff --git a/packages/opencode/src/config/markdown.ts b/packages/opencode/src/config/markdown.ts index 3c9709b5b3..2f1483dca3 100644 --- a/packages/opencode/src/config/markdown.ts +++ b/packages/opencode/src/config/markdown.ts @@ -1,4 +1,4 @@ -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import matter from "gray-matter" import { z } from "zod" import { Filesystem } from "../util/filesystem" diff --git a/packages/opencode/src/config/paths.ts b/packages/opencode/src/config/paths.ts index 82ccf3945f..884a774499 100644 --- a/packages/opencode/src/config/paths.ts +++ b/packages/opencode/src/config/paths.ts @@ -2,7 +2,7 @@ import path from "path" import os from "os" import z from "zod" import { type ParseError as JsoncParseError, parse as parseJsonc, printParseErrorCode } from "jsonc-parser" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { Filesystem } from "@/util/filesystem" import { Flag } from "@/flag/flag" import { Global } from "@/global" diff --git a/packages/opencode/src/config/tui.ts b/packages/opencode/src/config/tui.ts index 9347a8cc4c..87c39e700a 100644 --- a/packages/opencode/src/config/tui.ts +++ b/packages/opencode/src/config/tui.ts @@ -13,7 +13,7 @@ import { Global } from "@/global" import { Filesystem } from "@/util/filesystem" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" export namespace TuiConfig { const log = Log.create({ service: "tui.config" }) diff --git a/packages/opencode/src/control-plane/workspace-context.ts b/packages/opencode/src/control-plane/workspace-context.ts index 173ec6178a..541657b88c 100644 --- a/packages/opencode/src/control-plane/workspace-context.ts +++ b/packages/opencode/src/control-plane/workspace-context.ts @@ -12,6 +12,10 @@ export const WorkspaceContext = { return context.provide({ workspaceID: input.workspaceID as string }, () => input.fn()) }, + restore(workspaceID: string, fn: () => R): R { + return context.provide({ workspaceID }, fn) + }, + get workspaceID() { try { return context.use().workspaceID diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index f330e07b7a..b9ac0a6b43 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -1,20 +1,27 @@ import z from "zod" import { setTimeout as sleep } from "node:timers/promises" import { fn } from "@/util/fn" -import { Database, eq } from "@/storage/db" +import { Database, asc, eq } from "@/storage/db" import { Project } from "@/project/project" import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" import { SyncEvent } from "@/sync" +import { EventTable } from "@/sync/event.sql" +import { Flag } from "@/flag/flag" import { Log } from "@/util/log" import { Filesystem } from "@/util/filesystem" import { ProjectID } from "@/project/schema" -import { Slug } from "@opencode-ai/util/slug" +import { Slug } from "@opencode-ai/shared/util/slug" import { WorkspaceTable } from "./workspace.sql" import { getAdaptor } from "./adaptors" import { WorkspaceInfo } from "./types" import { WorkspaceID } from "./schema" import { parseSSE } from "./sse" +import { Session } from "@/session" +import { SessionTable } from "@/session/session.sql" +import { SessionID } from "@/session/schema" +import { errorData } from "@/util/error" +import { AppRuntime } from "@/effect/app-runtime" export namespace Workspace { export const Info = WorkspaceInfo.meta({ @@ -29,6 +36,13 @@ export namespace Workspace { }) export type ConnectionStatus = z.infer + const Restore = z.object({ + workspaceID: WorkspaceID.zod, + sessionID: SessionID.zod, + total: z.number().int().min(0), + step: z.number().int().min(0), + }) + export const Event = { Ready: BusEvent.define( "workspace.ready", @@ -42,6 +56,7 @@ export namespace Workspace { message: z.string(), }), ), + Restore: BusEvent.define("workspace.restore", Restore), Status: BusEvent.define("workspace.status", ConnectionStatus), } @@ -102,11 +117,170 @@ export namespace Workspace { return info }) + const SessionRestoreInput = z.object({ + workspaceID: WorkspaceID.zod, + sessionID: SessionID.zod, + }) + + export const sessionRestore = fn(SessionRestoreInput, async (input) => { + log.info("session restore requested", { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + }) + try { + const space = await get(input.workspaceID) + if (!space) throw new Error(`Workspace not found: ${input.workspaceID}`) + + const adaptor = await getAdaptor(space.projectID, space.type) + const target = await adaptor.target(space) + + // Need to switch the workspace of the session + SyncEvent.run(Session.Event.Updated, { + sessionID: input.sessionID, + info: { + workspaceID: input.workspaceID, + }, + }) + + const rows = Database.use((db) => + db + .select({ + id: EventTable.id, + aggregateID: EventTable.aggregate_id, + seq: EventTable.seq, + type: EventTable.type, + data: EventTable.data, + }) + .from(EventTable) + .where(eq(EventTable.aggregate_id, input.sessionID)) + .orderBy(asc(EventTable.seq)) + .all(), + ) + if (rows.length === 0) throw new Error(`No events found for session: ${input.sessionID}`) + + const all = rows + + const size = 10 + const sets = Array.from({ length: Math.ceil(all.length / size) }, (_, i) => all.slice(i * size, (i + 1) * size)) + const total = sets.length + log.info("session restore prepared", { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + workspaceType: space.type, + directory: space.directory, + target: target.type === "remote" ? String(route(target.url, "/sync/replay")) : target.directory, + events: all.length, + batches: total, + first: all[0]?.seq, + last: all.at(-1)?.seq, + }) + GlobalBus.emit("event", { + directory: "global", + workspace: input.workspaceID, + payload: { + type: Event.Restore.type, + properties: { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + total, + step: 0, + }, + }, + }) + for (const [i, events] of sets.entries()) { + log.info("session restore batch starting", { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + step: i + 1, + total, + events: events.length, + first: events[0]?.seq, + last: events.at(-1)?.seq, + target: target.type === "remote" ? String(route(target.url, "/sync/replay")) : target.directory, + }) + if (target.type === "local") { + SyncEvent.replayAll(events) + log.info("session restore batch replayed locally", { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + step: i + 1, + total, + events: events.length, + }) + } else { + const url = route(target.url, "/sync/replay") + const headers = new Headers(target.headers) + headers.set("content-type", "application/json") + const res = await fetch(url, { + method: "POST", + headers, + body: JSON.stringify({ + directory: space.directory ?? "", + events, + }), + }) + if (!res.ok) { + const body = await res.text() + log.error("session restore batch failed", { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + step: i + 1, + total, + status: res.status, + body, + }) + throw new Error( + `Failed to replay session ${input.sessionID} into workspace ${input.workspaceID}: HTTP ${res.status} ${body}`, + ) + } + log.info("session restore batch posted", { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + step: i + 1, + total, + status: res.status, + }) + } + GlobalBus.emit("event", { + directory: "global", + workspace: input.workspaceID, + payload: { + type: Event.Restore.type, + properties: { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + total, + step: i + 1, + }, + }, + }) + } + + log.info("session restore complete", { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + batches: total, + }) + + return { + total, + } + } catch (err) { + log.error("session restore failed", { + workspaceID: input.workspaceID, + sessionID: input.sessionID, + error: errorData(err), + }) + throw err + } + }) + export function list(project: Project.Info) { const rows = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.project_id, project.id)).all(), ) const spaces = rows.map(fromRow).sort((a, b) => a.id.localeCompare(b.id)) + for (const space of spaces) startSync(space) return spaces } @@ -120,13 +294,25 @@ export namespace Workspace { }) export const remove = fn(WorkspaceID.zod, async (id) => { + const sessions = Database.use((db) => + db.select({ id: SessionTable.id }).from(SessionTable).where(eq(SessionTable.workspace_id, id)).all(), + ) + for (const session of sessions) { + await AppRuntime.runPromise(Session.Service.use((svc) => svc.remove(session.id))) + } + const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get()) + if (row) { stopSync(id) const info = fromRow(row) - const adaptor = await getAdaptor(info.projectID, row.type) - adaptor.remove(info) + try { + const adaptor = await getAdaptor(info.projectID, row.type) + await adaptor.remove(info) + } catch (err) { + log.error("adaptor not available when removing workspace", { type: row.type }) + } Database.use((db) => db.delete(WorkspaceTable).where(eq(WorkspaceTable.id, id)).run()) return info } @@ -156,51 +342,81 @@ export namespace Workspace { const log = Log.create({ service: "workspace-sync" }) - async function workspaceEventLoop(space: Info, signal: AbortSignal) { - log.info("starting sync: " + space.id) + function route(url: string | URL, path: string) { + const next = new URL(url) + next.pathname = `${next.pathname.replace(/\/$/, "")}${path}` + next.search = "" + next.hash = "" + return next + } + async function syncWorkspace(space: Info, signal: AbortSignal) { while (!signal.aborted) { - log.info("connecting to sync: " + space.id) + log.info("connecting to global sync", { workspace: space.name }) - setStatus(space.id, "connecting") const adaptor = await getAdaptor(space.projectID, space.type) const target = await adaptor.target(space) if (target.type === "local") return - const res = await fetch(target.url + "/sync/event", { method: "GET", signal }).catch((err: unknown) => { - setStatus(space.id, "error", String(err)) + const res = await fetch(route(target.url, "/global/event"), { + method: "GET", + headers: target.headers, + signal, + }).catch((err: unknown) => { + setStatus(space.id, "error") + + log.info("failed to connect to global sync", { + workspace: space.name, + error: err, + }) return undefined }) - if (!res || !res.ok || !res.body) { - log.info("failed to connect to sync: " + res?.status) - setStatus(space.id, "error", res ? `HTTP ${res.status}` : "no response") + if (!res || !res.ok || !res.body) { + log.info("failed to connect to global sync", { workspace: space.name }) + setStatus(space.id, "error") await sleep(1000) continue } - setStatus(space.id, "connected") - await parseSSE(res.body, signal, (evt) => { - const event = evt as SyncEvent.SerializedEvent + log.info("global sync connected", { workspace: space.name }) + setStatus(space.id, "connected") + + await parseSSE(res.body, signal, (evt: any) => { try { - if (!event.type.startsWith("server.")) { - SyncEvent.replay(event) + if (!("payload" in evt)) return + + if (evt.payload.type === "sync") { + // This name -> type is temporary + SyncEvent.replay({ ...evt.payload, type: evt.payload.name } as SyncEvent.SerializedEvent) } + + GlobalBus.emit("event", { + directory: evt.directory, + project: evt.project, + workspace: space.id, + payload: evt.payload, + }) } catch (err) { - log.warn("failed to replay sync event", { + log.info("failed to replay global event", { workspaceID: space.id, error: err, }) } }) + + log.info("disconnected from global sync: " + space.id) setStatus(space.id, "disconnected") - log.info("disconnected to sync: " + space.id) - await sleep(250) + + // TODO: Implement exponential backoff + await sleep(1000) } } function startSync(space: Info) { + if (!Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) return + if (space.type === "worktree") { void Filesystem.exists(space.directory!).then((exists) => { setStatus(space.id, exists ? "connected" : "error", exists ? undefined : "directory does not exist") @@ -213,9 +429,9 @@ export namespace Workspace { aborts.set(space.id, abort) setStatus(space.id, "disconnected") - void workspaceEventLoop(space, abort.signal).catch((error) => { + void syncWorkspace(space, abort.signal).catch((error) => { setStatus(space.id, "error", String(error)) - log.warn("workspace sync listener failed", { + log.warn("workspace listener failed", { workspaceID: space.id, error, }) diff --git a/packages/opencode/src/effect/app-runtime.ts b/packages/opencode/src/effect/app-runtime.ts index b13396615f..5948bd25e6 100644 --- a/packages/opencode/src/effect/app-runtime.ts +++ b/packages/opencode/src/effect/app-runtime.ts @@ -2,7 +2,7 @@ import { Layer, ManagedRuntime } from "effect" import { attach, memoMap } from "./run-service" import { Observability } from "./observability" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Bus } from "@/bus" import { Auth } from "@/auth" import { Account } from "@/account" diff --git a/packages/opencode/src/effect/bridge.ts b/packages/opencode/src/effect/bridge.ts new file mode 100644 index 0000000000..bafa5a0ea6 --- /dev/null +++ b/packages/opencode/src/effect/bridge.ts @@ -0,0 +1,49 @@ +import { Effect, Fiber } from "effect" +import { WorkspaceContext } from "@/control-plane/workspace-context" +import { Instance, type InstanceContext } from "@/project/instance" +import { LocalContext } from "@/util/local-context" +import { InstanceRef, WorkspaceRef } from "./instance-ref" +import { attachWith } from "./run-service" + +export namespace EffectBridge { + export interface Shape { + readonly promise: (effect: Effect.Effect) => Promise + readonly fork: (effect: Effect.Effect) => Fiber.Fiber + } + + function restore(instance: InstanceContext | undefined, workspace: string | undefined, fn: () => R): R { + if (instance && workspace !== undefined) { + return WorkspaceContext.restore(workspace, () => Instance.restore(instance, fn)) + } + if (instance) return Instance.restore(instance, fn) + if (workspace !== undefined) return WorkspaceContext.restore(workspace, fn) + return fn() + } + + export function make(): Effect.Effect { + return Effect.gen(function* () { + const ctx = yield* Effect.context() + const value = yield* InstanceRef + const instance = + value ?? + (() => { + try { + return Instance.current + } catch (err) { + if (!(err instanceof LocalContext.NotFound)) throw err + } + })() + const workspace = (yield* WorkspaceRef) ?? WorkspaceContext.workspaceID + const attach = (effect: Effect.Effect) => attachWith(effect, { instance, workspace }) + const wrap = (effect: Effect.Effect) => + attach(effect).pipe(Effect.provide(ctx)) as Effect.Effect + + return { + promise: (effect: Effect.Effect) => + restore(instance, workspace, () => Effect.runPromise(wrap(effect))), + fork: (effect: Effect.Effect) => + restore(instance, workspace, () => Effect.runFork(wrap(effect))), + } satisfies Shape + }) + } +} diff --git a/packages/opencode/src/effect/observability.ts b/packages/opencode/src/effect/observability.ts index 5486eafbfd..f79306bf1e 100644 --- a/packages/opencode/src/effect/observability.ts +++ b/packages/opencode/src/effect/observability.ts @@ -12,8 +12,8 @@ export namespace Observability { const headers = Flag.OTEL_EXPORTER_OTLP_HEADERS ? Flag.OTEL_EXPORTER_OTLP_HEADERS.split(",").reduce( (acc, x) => { - const [key, value] = x.split("=") - acc[key] = value + const [key, ...value] = x.split("=") + acc[key] = value.join("=") return acc }, {} as Record, @@ -46,6 +46,18 @@ export namespace Observability { const OTLP = await import("@opentelemetry/exporter-trace-otlp-http") const SdkBase = await import("@opentelemetry/sdk-trace-base") + // @effect/opentelemetry creates a NodeTracerProvider but never calls + // register(), so the global @opentelemetry/api context manager stays + // as the no-op default. Non-Effect code (like the AI SDK) that calls + // tracer.startActiveSpan() relies on context.active() to find the + // parent span — without a real context manager every span starts a + // new trace. Registering AsyncLocalStorageContextManager fixes this. + const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks") + const { context } = await import("@opentelemetry/api") + const mgr = new AsyncLocalStorageContextManager() + mgr.enable() + context.setGlobalContextManager(mgr) + return NodeSdk.layer(() => ({ resource, spanProcessor: new SdkBase.BatchSpanProcessor( diff --git a/packages/opencode/src/effect/run-service.ts b/packages/opencode/src/effect/run-service.ts index bb4307b57c..13104c88b3 100644 --- a/packages/opencode/src/effect/run-service.ts +++ b/packages/opencode/src/effect/run-service.ts @@ -5,14 +5,31 @@ import { LocalContext } from "@/util/local-context" import { InstanceRef, WorkspaceRef } from "./instance-ref" import { Observability } from "./observability" import { WorkspaceContext } from "@/control-plane/workspace-context" +import type { InstanceContext } from "@/project/instance" export const memoMap = Layer.makeMemoMapUnsafe() +type Refs = { + instance?: InstanceContext + workspace?: string +} + +export function attachWith(effect: Effect.Effect, refs: Refs): Effect.Effect { + if (!refs.instance && !refs.workspace) return effect + if (!refs.instance) return effect.pipe(Effect.provideService(WorkspaceRef, refs.workspace)) + if (!refs.workspace) return effect.pipe(Effect.provideService(InstanceRef, refs.instance)) + return effect.pipe( + Effect.provideService(InstanceRef, refs.instance), + Effect.provideService(WorkspaceRef, refs.workspace), + ) +} + export function attach(effect: Effect.Effect): Effect.Effect { try { - const ctx = Instance.current - const workspaceID = WorkspaceContext.workspaceID - return effect.pipe(Effect.provideService(InstanceRef, ctx), Effect.provideService(WorkspaceRef, workspaceID)) + return attachWith(effect, { + instance: Instance.current, + workspace: WorkspaceContext.workspaceID, + }) } catch (err) { if (!(err instanceof LocalContext.NotFound)) throw err } diff --git a/packages/opencode/src/env/index.ts b/packages/opencode/src/env/index.ts index 930287899c..b9efb68520 100644 --- a/packages/opencode/src/env/index.ts +++ b/packages/opencode/src/env/index.ts @@ -1,6 +1,5 @@ import { Context, Effect, Layer } from "effect" import { InstanceState } from "@/effect/instance-state" -import { makeRuntime } from "@/effect/run-service" export namespace Env { type State = Record @@ -35,22 +34,4 @@ export namespace Env { ) export const defaultLayer = layer - - const rt = makeRuntime(Service, defaultLayer) - - export function get(key: string) { - return rt.runSync((svc) => svc.get(key)) - } - - export function all() { - return rt.runSync((svc) => svc.all()) - } - - export function set(key: string, value: string) { - return rt.runSync((svc) => svc.set(key, value)) - } - - export function remove(key: string) { - return rt.runSync((svc) => svc.remove(key)) - } } diff --git a/packages/opencode/src/file/ignore.ts b/packages/opencode/src/file/ignore.ts index b9731040c7..a102e7d170 100644 --- a/packages/opencode/src/file/ignore.ts +++ b/packages/opencode/src/file/ignore.ts @@ -1,5 +1,5 @@ import { sep } from "node:path" -import { Glob } from "../util/glob" +import { Glob } from "@opencode-ai/shared/util/glob" export namespace FileIgnore { const FOLDERS = new Set([ diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 6730957f23..113dc59096 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -1,7 +1,7 @@ import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Git } from "@/git" import { Effect, Layer, Context } from "effect" import * as Stream from "effect/Stream" diff --git a/packages/opencode/src/file/time.ts b/packages/opencode/src/file/time.ts index e5055b6718..5537526730 100644 --- a/packages/opencode/src/file/time.ts +++ b/packages/opencode/src/file/time.ts @@ -1,6 +1,6 @@ import { DateTime, Effect, Layer, Option, Semaphore, Context } from "effect" import { InstanceState } from "@/effect/instance-state" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Flag } from "@/flag/flag" import type { SessionID } from "@/session/schema" import { Filesystem } from "@/util/filesystem" diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index ada661ebab..6c17310ff8 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -1,4 +1,4 @@ -import { Npm } from "@/npm" +import { Npm } from "../npm" import { Instance } from "../project/instance" import { Filesystem } from "../util/filesystem" import { Process } from "../util/process" diff --git a/packages/opencode/src/global/index.ts b/packages/opencode/src/global/index.ts index 869019e2ce..32d5153213 100644 --- a/packages/opencode/src/global/index.ts +++ b/packages/opencode/src/global/index.ts @@ -3,6 +3,7 @@ import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir" import path from "path" import os from "os" import { Filesystem } from "../util/filesystem" +import { Flock } from "@opencode-ai/shared/util/flock" const app = "opencode" @@ -26,6 +27,9 @@ export namespace Global { } } +// Initialize Flock with global state path +Flock.setGlobal({ state }) + await Promise.all([ fs.mkdir(Global.Path.data, { recursive: true }), fs.mkdir(Global.Path.config, { recursive: true }), diff --git a/packages/opencode/src/ide/index.ts b/packages/opencode/src/ide/index.ts index ce4128b906..46efea2cce 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -1,7 +1,7 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import z from "zod" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { Log } from "../util/log" import { Process } from "@/util/process" diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 753becc267..641411461d 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -11,7 +11,7 @@ import { UninstallCommand } from "./cli/cmd/uninstall" import { ModelsCommand } from "./cli/cmd/models" import { UI } from "./cli/ui" import { Installation } from "./installation" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { FormatError } from "./cli/error" import { ServeCommand } from "./cli/cmd/serve" import { Filesystem } from "./util/filesystem" diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index de0c438626..fe5a9ab182 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -9,7 +9,7 @@ import { Process } from "../util/process" import { LANGUAGE_EXTENSIONS } from "./language" import z from "zod" import type { LSPServer } from "./server" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { withTimeout } from "../util/timeout" import { Instance } from "../project/instance" import { Filesystem } from "../util/filesystem" diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index abfb31ead0..9ffef7a425 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -11,9 +11,9 @@ import { Flag } from "../flag/flag" import { Archive } from "../util/archive" import { Process } from "../util/process" import { which } from "../util/which" -import { Module } from "@opencode-ai/util/module" +import { Module } from "@opencode-ai/shared/util/module" import { spawn } from "./launch" -import { Npm } from "@/npm" +import { Npm } from "../npm" export namespace LSPServer { const log = Log.create({ service: "lsp.server" }) diff --git a/packages/opencode/src/mcp/auth.ts b/packages/opencode/src/mcp/auth.ts index 6eefc107d9..85f9e1d8c9 100644 --- a/packages/opencode/src/mcp/auth.ts +++ b/packages/opencode/src/mcp/auth.ts @@ -2,7 +2,7 @@ import path from "path" import z from "zod" import { Global } from "../global" import { Effect, Layer, Context } from "effect" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" export namespace McpAuth { export const Tokens = z.object({ diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 3de427d7e4..a68c6c1d8d 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -11,12 +11,12 @@ import { } from "@modelcontextprotocol/sdk/types.js" import { Config } from "../config/config" import { Log } from "../util/log" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import z from "zod/v4" import { Instance } from "../project/instance" import { Installation } from "../installation" import { withTimeout } from "@/util/timeout" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { McpOAuthProvider } from "./oauth-provider" import { McpOAuthCallback } from "./oauth-callback" import { McpAuth } from "./auth" @@ -25,7 +25,7 @@ import { Bus } from "@/bus" import { TuiEvent } from "@/cli/cmd/tui/event" import open from "open" import { Effect, Exit, Layer, Option, Context, Stream } from "effect" -import { EffectLogger } from "@/effect/logger" +import { EffectBridge } from "@/effect/bridge" import { InstanceState } from "@/effect/instance-state" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" @@ -471,25 +471,24 @@ export namespace MCP { Effect.catch(() => Effect.succeed([] as number[])), ) - function watch(s: State, name: string, client: MCPClient, timeout?: number) { + function watch(s: State, name: string, client: MCPClient, bridge: EffectBridge.Shape, timeout?: number) { client.setNotificationHandler(ToolListChangedNotificationSchema, async () => { log.info("tools list changed notification received", { server: name }) if (s.clients[name] !== client || s.status[name]?.status !== "connected") return - const listed = await Effect.runPromise(defs(name, client, timeout).pipe(Effect.provide(EffectLogger.layer))) + const listed = await bridge.promise(defs(name, client, timeout)) if (!listed) return if (s.clients[name] !== client || s.status[name]?.status !== "connected") return s.defs[name] = listed - await Effect.runPromise( - bus.publish(ToolsChanged, { server: name }).pipe(Effect.ignore, Effect.provide(EffectLogger.layer)), - ) + await bridge.promise(bus.publish(ToolsChanged, { server: name }).pipe(Effect.ignore)) }) } const state = yield* InstanceState.make( Effect.fn("MCP.state")(function* () { const cfg = yield* cfgSvc.get() + const bridge = yield* EffectBridge.make() const config = cfg.mcp ?? {} const s: State = { status: {}, @@ -518,7 +517,7 @@ export namespace MCP { if (result.mcpClient) { s.clients[key] = result.mcpClient s.defs[key] = result.defs! - watch(s, key, result.mcpClient, mcp.timeout) + watch(s, key, result.mcpClient, bridge, mcp.timeout) } }), { concurrency: "unbounded" }, @@ -565,11 +564,12 @@ export namespace MCP { listed: MCPToolDef[], timeout?: number, ) { + const bridge = yield* EffectBridge.make() yield* closeClient(s, name) s.status[name] = { status: "connected" } s.clients[name] = client s.defs[name] = listed - watch(s, name, client, timeout) + watch(s, name, client, bridge, timeout) return s.status[name] }) diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 3568ff20e2..e648fd899c 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -1,12 +1,12 @@ import semver from "semver" import z from "zod" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { Global } from "../global" import { Log } from "../util/log" import path from "path" import { readdir, rm } from "fs/promises" import { Filesystem } from "@/util/filesystem" -import { Flock } from "@/util/flock" +import { Flock } from "@opencode-ai/shared/util/flock" import { Arborist } from "@npmcli/arborist" export namespace Npm { diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 0dc53d997d..9f618eff8c 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -12,13 +12,13 @@ import { createOpencodeClient } from "@opencode-ai/sdk" import { Flag } from "../flag/flag" import { CodexAuthPlugin } from "./codex" import { Session } from "../session" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { CopilotAuthPlugin } from "./github-copilot/copilot" import { gitlabAuthPlugin as GitlabAuthPlugin } from "opencode-gitlab-auth" import { PoeAuthPlugin } from "opencode-poe-auth" import { CloudflareAIGatewayAuthPlugin, CloudflareWorkersAuthPlugin } from "./cloudflare" import { Effect, Layer, Context, Stream } from "effect" -import { EffectLogger } from "@/effect/logger" +import { EffectBridge } from "@/effect/bridge" import { InstanceState } from "@/effect/instance-state" import { errorMessage } from "@/util/error" import { PluginLoader } from "./loader" @@ -90,14 +90,6 @@ export namespace Plugin { return result } - function publishPluginError(bus: Bus.Interface, message: string) { - Effect.runFork( - bus - .publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) - .pipe(Effect.provide(EffectLogger.layer)), - ) - } - async function applyPlugin(load: PluginLoader.Loaded, input: PluginInput, hooks: Hooks[]) { const plugin = readV1Plugin(load.mod, load.spec, "server", "detect") if (plugin) { @@ -120,6 +112,11 @@ export namespace Plugin { const state = yield* InstanceState.make( Effect.fn("Plugin.state")(function* (ctx) { const hooks: Hooks[] = [] + const bridge = yield* EffectBridge.make() + + function publishPluginError(message: string) { + bridge.fork(bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })) + } const { Server } = yield* Effect.promise(() => import("../server/server")) @@ -187,24 +184,24 @@ export namespace Plugin { if (stage === "install") { const parsed = parsePluginSpecifier(spec) log.error("failed to install plugin", { pkg: parsed.pkg, version: parsed.version, error: message }) - publishPluginError(bus, `Failed to install plugin ${parsed.pkg}@${parsed.version}: ${message}`) + publishPluginError(`Failed to install plugin ${parsed.pkg}@${parsed.version}: ${message}`) return } if (stage === "compatibility") { log.warn("plugin incompatible", { path: spec, error: message }) - publishPluginError(bus, `Plugin ${spec} skipped: ${message}`) + publishPluginError(`Plugin ${spec} skipped: ${message}`) return } if (stage === "entry") { log.error("failed to resolve plugin server entry", { path: spec, error: message }) - publishPluginError(bus, `Failed to load plugin ${spec}: ${message}`) + publishPluginError(`Failed to load plugin ${spec}: ${message}`) return } log.error("failed to load plugin", { path: spec, target: resolved?.entry, error: message }) - publishPluginError(bus, `Failed to load plugin ${spec}: ${message}`) + publishPluginError(`Failed to load plugin ${spec}: ${message}`) }, }, }), diff --git a/packages/opencode/src/plugin/install.ts b/packages/opencode/src/plugin/install.ts index b6bac42a7f..8dd8212965 100644 --- a/packages/opencode/src/plugin/install.ts +++ b/packages/opencode/src/plugin/install.ts @@ -10,7 +10,7 @@ import { import { ConfigPaths } from "@/config/paths" import { Global } from "@/global" import { Filesystem } from "@/util/filesystem" -import { Flock } from "@/util/flock" +import { Flock } from "@opencode-ai/shared/util/flock" import { isRecord } from "@/util/record" import { parsePluginSpecifier, readPackageThemes, readPluginPackage, resolvePluginTarget } from "./shared" diff --git a/packages/opencode/src/plugin/meta.ts b/packages/opencode/src/plugin/meta.ts index cbfaf6ae15..f408954690 100644 --- a/packages/opencode/src/plugin/meta.ts +++ b/packages/opencode/src/plugin/meta.ts @@ -4,7 +4,7 @@ import { fileURLToPath } from "url" import { Flag } from "@/flag/flag" import { Global } from "@/global" import { Filesystem } from "@/util/filesystem" -import { Flock } from "@/util/flock" +import { Flock } from "@opencode-ai/shared/util/flock" import { parsePluginSpecifier, pluginSource } from "./shared" diff --git a/packages/opencode/src/plugin/shared.ts b/packages/opencode/src/plugin/shared.ts index 6cda49786b..54cc32af5b 100644 --- a/packages/opencode/src/plugin/shared.ts +++ b/packages/opencode/src/plugin/shared.ts @@ -2,7 +2,7 @@ import path from "path" import { fileURLToPath, pathToFileURL } from "url" import npa from "npm-package-arg" import semver from "semver" -import { Npm } from "@/npm" +import { Npm } from "../npm" import { Filesystem } from "@/util/filesystem" import { isRecord } from "@/util/record" diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index f9d634a1cd..d20bf42494 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -11,7 +11,7 @@ import { ProjectID } from "./schema" import { Effect, Layer, Path, Scope, Context, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" export namespace Project { diff --git a/packages/opencode/src/project/vcs.ts b/packages/opencode/src/project/vcs.ts index aede980d6d..e4093fd456 100644 --- a/packages/opencode/src/project/vcs.ts +++ b/packages/opencode/src/project/vcs.ts @@ -4,7 +4,7 @@ import path from "path" import { Bus } from "@/bus" import { BusEvent } from "@/bus/bus-event" import { InstanceState } from "@/effect/instance-state" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { FileWatcher } from "@/file/watcher" import { Git } from "@/git" import { Log } from "@/util/log" diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index e410b86365..c66ccffc12 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -1,5 +1,5 @@ import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { Auth } from "@/auth" import { InstanceState } from "@/effect/instance-state" import { Plugin } from "../plugin" diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index 2d787588b0..55f137aa0b 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -6,8 +6,8 @@ import { Installation } from "../installation" import { Flag } from "../flag/flag" import { lazy } from "@/util/lazy" import { Filesystem } from "../util/filesystem" -import { Flock } from "@/util/flock" -import { Hash } from "@/util/hash" +import { Flock } from "@opencode-ai/shared/util/flock" +import { Hash } from "@opencode-ai/shared/util/hash" // Try to import bundled snapshot (generated at build time) // Falls back to undefined in dev mode when snapshot doesn't exist diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 84f074d42d..9ec5dfc6b5 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -6,9 +6,9 @@ import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda" import { NoSuchModelError, type Provider as SDK } from "ai" import { Log } from "../util/log" import { Npm } from "../npm" -import { Hash } from "../util/hash" +import { Hash } from "@opencode-ai/shared/util/hash" import { Plugin } from "../plugin" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { type LanguageModelV3 } from "@ai-sdk/provider" import { ModelsDev } from "./models" import { Auth } from "../auth" @@ -19,9 +19,9 @@ import { iife } from "@/util/iife" import { Global } from "../global" import path from "path" import { Effect, Layer, Context } from "effect" -import { EffectLogger } from "@/effect/logger" +import { EffectBridge } from "@/effect/bridge" import { InstanceState } from "@/effect/instance-state" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { isRecord } from "@/util/record" // Direct imports for bundled providers @@ -1043,6 +1043,7 @@ export namespace Provider { const state = yield* InstanceState.make(() => Effect.gen(function* () { using _ = log.time("state") + const bridge = yield* EffectBridge.make() const cfg = yield* config.get() const modelsDev = yield* Effect.promise(() => ModelsDev.get()) const database = mapValues(modelsDev, fromModelsDevProvider) @@ -1223,8 +1224,7 @@ export namespace Provider { const options = yield* Effect.promise(() => plugin.auth!.loader!( - () => - Effect.runPromise(auth.get(providerID).pipe(Effect.orDie, Effect.provide(EffectLogger.layer))) as any, + () => bridge.promise(auth.get(providerID).pipe(Effect.orDie)) as any, database[plugin.auth!.provider], ), ) diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 1891721851..1c969b4b93 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -5,12 +5,12 @@ import { Instance } from "@/project/instance" import type { Proc } from "#pty" import z from "zod" import { Log } from "../util/log" -import { lazy } from "@opencode-ai/util/lazy" +import { lazy } from "@opencode-ai/shared/util/lazy" import { Shell } from "@/shell/shell" import { Plugin } from "@/plugin" import { PtyID } from "./schema" import { Effect, Layer, Context } from "effect" -import { EffectLogger } from "@/effect/logger" +import { EffectBridge } from "@/effect/bridge" export namespace Pty { const log = Log.create({ service: "pty" }) @@ -173,6 +173,7 @@ export namespace Pty { const create = Effect.fn("Pty.create")(function* (input: CreateInput) { const s = yield* InstanceState.get(state) + const bridge = yield* EffectBridge.make() const id = PtyID.ascending() const command = input.command || Shell.preferred() const args = input.args || [] @@ -256,8 +257,8 @@ export namespace Pty { if (session.info.status === "exited") return log.info("session exited", { id, exitCode }) session.info.status = "exited" - Effect.runFork(bus.publish(Event.Exited, { id, exitCode }).pipe(Effect.provide(EffectLogger.layer))) - Effect.runFork(remove(id).pipe(Effect.provide(EffectLogger.layer))) + bridge.fork(bus.publish(Event.Exited, { id, exitCode })) + bridge.fork(remove(id)) }), ) yield* bus.publish(Event.Created, { info }) diff --git a/packages/opencode/src/server/instance/index.ts b/packages/opencode/src/server/instance/index.ts index 86a18dc673..4a03b7b29c 100644 --- a/packages/opencode/src/server/instance/index.ts +++ b/packages/opencode/src/server/instance/index.ts @@ -23,6 +23,7 @@ import { ConfigRoutes } from "./config" import { ExperimentalRoutes } from "./experimental" import { ProviderRoutes } from "./provider" import { EventRoutes } from "./event" +import { SyncRoutes } from "./sync" import { WorkspaceRouterMiddleware } from "./middleware" import { AppRuntime } from "@/effect/app-runtime" @@ -37,6 +38,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => .route("/permission", PermissionRoutes()) .route("/question", QuestionRoutes()) .route("/provider", ProviderRoutes()) + .route("/sync", SyncRoutes()) .route("/", FileRoutes()) .route("/", EventRoutes()) .route("/mcp", McpRoutes()) diff --git a/packages/opencode/src/server/instance/middleware.ts b/packages/opencode/src/server/instance/middleware.ts index 9155ad451b..824c265efe 100644 --- a/packages/opencode/src/server/instance/middleware.ts +++ b/packages/opencode/src/server/instance/middleware.ts @@ -11,9 +11,12 @@ import { Session } from "@/session" import { SessionID } from "@/session/schema" import { WorkspaceContext } from "@/control-plane/workspace-context" import { AppRuntime } from "@/effect/app-runtime" +import { Log } from "@/util/log" type Rule = { method?: string; path: string; exact?: boolean; action: "local" | "forward" } +const OPENCODE_WORKSPACE = process.env.OPENCODE_WORKSPACE + const RULES: Array = [ { path: "/session/status", action: "forward" }, { method: "GET", path: "/session", action: "local" }, @@ -46,6 +49,8 @@ async function getSessionWorkspace(url: URL) { } export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): MiddlewareHandler { + const log = Log.create({ service: "workspace-router" }) + return async (c, next) => { const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd() const directory = Filesystem.resolve( @@ -63,8 +68,22 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware const sessionWorkspaceID = await getSessionWorkspace(url) const workspaceID = sessionWorkspaceID || url.searchParams.get("workspace") - // If no workspace is provided we use the project - if (!workspaceID) { + if (!workspaceID || url.pathname.startsWith("/console") || OPENCODE_WORKSPACE) { + if (OPENCODE_WORKSPACE) { + return WorkspaceContext.provide({ + workspaceID: WorkspaceID.make(OPENCODE_WORKSPACE), + async fn() { + return Instance.provide({ + directory, + init: () => AppRuntime.runPromise(InstanceBootstrap), + async fn() { + return next() + }, + }) + }, + }) + } + return Instance.provide({ directory, init: () => AppRuntime.runPromise(InstanceBootstrap), @@ -77,16 +96,6 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware const workspace = await Workspace.get(WorkspaceID.make(workspaceID)) if (!workspace) { - // Special-case deleting a session in case user's data in a - // weird state. Allow them to forcefully delete a synced session - // even if the remote workspace is not in their data. - // - // The lets the `DELETE /session/:id` endpoint through and we've - // made sure that it will run without an instance - if (url.pathname.match(/\/session\/[^/]+$/) && c.req.method === "DELETE") { - return next() - } - return new Response(`Workspace not found: ${workspaceID}`, { status: 500, headers: { @@ -95,6 +104,12 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware }) } + if (local(c.req.method, url.pathname)) { + // No instance provided because we are serving cached data; there + // is no instance to work with + return next() + } + const adaptor = await getAdaptor(workspace.projectID, workspace.type) const target = await adaptor.target(workspace) @@ -112,24 +127,27 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware }) } - if (local(c.req.method, url.pathname)) { - // No instance provided because we are serving cached data; there - // is no instance to work with - return next() - } + const proxyURL = new URL(target.url) + proxyURL.pathname = `${proxyURL.pathname.replace(/\/$/, "")}${url.pathname}` + proxyURL.search = url.search + proxyURL.hash = url.hash + proxyURL.searchParams.delete("workspace") + + log.info("workspace proxy forwarding", { + workspaceID, + request: url.toString(), + target: String(target.url), + proxy: proxyURL.toString(), + }) if (c.req.header("upgrade")?.toLowerCase() === "websocket") { - return ServerProxy.websocket(upgrade, target, c.req.raw, c.env) + return ServerProxy.websocket(upgrade, proxyURL, target.headers, c.req.raw, c.env) } const headers = new Headers(c.req.raw.headers) headers.delete("x-opencode-workspace") - return ServerProxy.http( - target, - new Request(c.req.raw, { - headers, - }), - ) + const req = new Request(c.req.raw, { headers }) + return ServerProxy.http(proxyURL, target.headers, req) } } diff --git a/packages/opencode/src/server/instance/session.ts b/packages/opencode/src/server/instance/session.ts index e443a6ddd2..a011c32f9b 100644 --- a/packages/opencode/src/server/instance/session.ts +++ b/packages/opencode/src/server/instance/session.ts @@ -25,7 +25,7 @@ import { ModelID, ProviderID } from "@/provider/schema" import { errors } from "../error" import { lazy } from "../../util/lazy" import { Bus } from "../../bus" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" const log = Log.create({ service: "server" }) diff --git a/packages/opencode/src/server/instance/sync.ts b/packages/opencode/src/server/instance/sync.ts new file mode 100644 index 0000000000..c22969130a --- /dev/null +++ b/packages/opencode/src/server/instance/sync.ts @@ -0,0 +1,118 @@ +import z from "zod" +import { Hono } from "hono" +import { describeRoute, validator, resolver } from "hono-openapi" +import { SyncEvent } from "@/sync" +import { Database, asc, and, not, or, lte, eq } from "@/storage/db" +import { EventTable } from "@/sync/event.sql" +import { lazy } from "@/util/lazy" +import { Log } from "@/util/log" +import { errors } from "../error" + +const ReplayEvent = z.object({ + id: z.string(), + aggregateID: z.string(), + seq: z.number().int().min(0), + type: z.string(), + data: z.record(z.string(), z.unknown()), +}) + +const log = Log.create({ service: "server.sync" }) + +export const SyncRoutes = lazy(() => + new Hono() + .post( + "/replay", + describeRoute({ + summary: "Replay sync events", + description: "Validate and replay a complete sync event history.", + operationId: "sync.replay", + responses: { + 200: { + description: "Replayed sync events", + content: { + "application/json": { + schema: resolver( + z.object({ + sessionID: z.string(), + }), + ), + }, + }, + }, + ...errors(400), + }, + }), + validator( + "json", + z.object({ + directory: z.string(), + events: z.array(ReplayEvent).min(1), + }), + ), + async (c) => { + const body = c.req.valid("json") + const events = body.events + const source = events[0].aggregateID + log.info("sync replay requested", { + sessionID: source, + events: events.length, + first: events[0]?.seq, + last: events.at(-1)?.seq, + directory: body.directory, + }) + SyncEvent.replayAll(events) + + log.info("sync replay complete", { + sessionID: source, + events: events.length, + first: events[0]?.seq, + last: events.at(-1)?.seq, + }) + + return c.json({ + sessionID: source, + }) + }, + ) + .get( + "/history", + describeRoute({ + summary: "List sync events", + description: + "List sync events for all aggregates. Keys are aggregate IDs the client already knows about, values are the last known sequence ID. Events with seq > value are returned for those aggregates. Aggregates not listed in the input get their full history.", + operationId: "sync.history.list", + responses: { + 200: { + description: "Sync events", + content: { + "application/json": { + schema: resolver( + z.array( + z.object({ + id: z.string(), + aggregate_id: z.string(), + seq: z.number(), + type: z.string(), + data: z.record(z.string(), z.unknown()), + }), + ), + ), + }, + }, + }, + ...errors(400), + }, + }), + validator("json", z.record(z.string(), z.number().int().min(0))), + async (c) => { + const body = c.req.valid("json") + const exclude = Object.entries(body) + const where = + exclude.length > 0 + ? not(or(...exclude.map(([id, seq]) => and(eq(EventTable.aggregate_id, id), lte(EventTable.seq, seq))))!) + : undefined + const rows = Database.use((db) => db.select().from(EventTable).where(where).orderBy(asc(EventTable.seq)).all()) + return c.json(rows) + }, + ), +) diff --git a/packages/opencode/src/server/instance/workspace.ts b/packages/opencode/src/server/instance/workspace.ts index 7cee031975..a4ff4eda8d 100644 --- a/packages/opencode/src/server/instance/workspace.ts +++ b/packages/opencode/src/server/instance/workspace.ts @@ -6,12 +6,10 @@ import { Workspace } from "../../control-plane/workspace" import { Instance } from "../../project/instance" import { errors } from "../error" import { lazy } from "../../util/lazy" +import { Log } from "@/util/log" +import { errorData } from "@/util/error" -const WorkspaceAdaptor = z.object({ - type: z.string(), - name: z.string(), - description: z.string(), -}) +const log = Log.create({ service: "server.workspace" }) export const WorkspaceRoutes = lazy(() => new Hono() @@ -26,7 +24,15 @@ export const WorkspaceRoutes = lazy(() => description: "Workspace adaptors", content: { "application/json": { - schema: resolver(z.array(WorkspaceAdaptor)), + schema: resolver( + z.array( + z.object({ + type: z.string(), + name: z.string(), + description: z.string(), + }), + ), + ), }, }, }, @@ -140,5 +146,58 @@ export const WorkspaceRoutes = lazy(() => const { id } = c.req.valid("param") return c.json(await Workspace.remove(id)) }, + ) + .post( + "/:id/session-restore", + describeRoute({ + summary: "Restore session into workspace", + description: "Replay a session's sync events into the target workspace in batches.", + operationId: "experimental.workspace.sessionRestore", + responses: { + 200: { + description: "Session replay started", + content: { + "application/json": { + schema: resolver( + z.object({ + total: z.number().int().min(0), + }), + ), + }, + }, + }, + ...errors(400), + }, + }), + validator("param", z.object({ id: Workspace.Info.shape.id })), + validator("json", Workspace.sessionRestore.schema.omit({ workspaceID: true })), + async (c) => { + const { id } = c.req.valid("param") + const body = c.req.valid("json") + log.info("session restore route requested", { + workspaceID: id, + sessionID: body.sessionID, + directory: Instance.directory, + }) + try { + const result = await Workspace.sessionRestore({ + workspaceID: id, + ...body, + }) + log.info("session restore route complete", { + workspaceID: id, + sessionID: body.sessionID, + total: result.total, + }) + return c.json(result) + } catch (err) { + log.error("session restore route failed", { + workspaceID: id, + sessionID: body.sessionID, + error: errorData(err), + }) + throw err + } + }, ), ) diff --git a/packages/opencode/src/server/middleware.ts b/packages/opencode/src/server/middleware.ts index a51ba602b5..6e91651866 100644 --- a/packages/opencode/src/server/middleware.ts +++ b/packages/opencode/src/server/middleware.ts @@ -1,5 +1,5 @@ import { Provider } from "../provider/provider" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { NotFoundError } from "../storage/db" import { Session } from "../session" import type { ContentfulStatusCode } from "hono/utils/http-status" @@ -86,7 +86,7 @@ const zipped = compress() export const CompressionMiddleware: MiddlewareHandler = (c, next) => { const path = c.req.path const method = c.req.method - if (path === "/event" || path === "/global/event" || path === "/global/sync-event") return next() + if (path === "/event" || path === "/global/event") return next() if (method === "POST" && /\/session\/[^/]+\/(message|prompt_async)$/.test(path)) return next() return zipped(c, next) } diff --git a/packages/opencode/src/server/proxy.ts b/packages/opencode/src/server/proxy.ts index c90a657dc2..0c0deba20c 100644 --- a/packages/opencode/src/server/proxy.ts +++ b/packages/opencode/src/server/proxy.ts @@ -1,6 +1,6 @@ -import type { Target } from "@/control-plane/types" import { Hono } from "hono" import type { UpgradeWebSocket } from "hono/ws" +import { Log } from "@/util/log" const hop = new Set([ "connection", @@ -20,6 +20,7 @@ type Msg = string | ArrayBuffer | Uint8Array function headers(req: Request, extra?: HeadersInit) { const out = new Headers(req.headers) for (const key of hop) out.delete(key) + out.delete("accept-encoding") out.delete("x-opencode-directory") out.delete("x-opencode-workspace") if (!extra) return out @@ -98,31 +99,63 @@ const app = (upgrade: UpgradeWebSocket) => ) export namespace ServerProxy { - export function http(target: Extract, req: Request) { + const log = Log.Default.clone().tag("service", "server-proxy") + + export function http(url: string | URL, extra: HeadersInit | undefined, req: Request) { + console.log("proxy http request", { + method: req.method, + request: req.url, + url: String(url), + }) return fetch( - new Request(target.url, { + new Request(url, { method: req.method, - headers: headers(req, target.headers), + headers: headers(req, extra), body: req.method === "GET" || req.method === "HEAD" ? undefined : req.body, redirect: "manual", signal: req.signal, }), - ) + ).then((res) => { + const next = new Headers(res.headers) + next.delete("content-encoding") + next.delete("content-length") + + console.log("proxy http response", { + method: req.method, + request: req.url, + url: String(url), + status: res.status, + statusText: res.statusText, + }) + return new Response(res.body, { + status: res.status, + statusText: res.statusText, + headers: next, + }) + }) } export function websocket( upgrade: UpgradeWebSocket, - target: Extract, + target: string | URL, + extra: HeadersInit | undefined, req: Request, env: unknown, ) { - const url = new URL(req.url) - url.pathname = "/__workspace_ws" - url.search = "" + const proxy = new URL(req.url) + proxy.pathname = "/__workspace_ws" + proxy.search = "" const next = new Headers(req.headers) - next.set("x-opencode-proxy-url", socket(target.url)) + next.set("x-opencode-proxy-url", socket(target)) + for (const [key, value] of new Headers(extra).entries()) { + next.set(key, value) + } + log.info("proxy websocket", { + request: req.url, + target: String(target), + }) return app(upgrade).fetch( - new Request(url, { + new Request(proxy, { method: req.method, headers: next, signal: req.signal, diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index aa3404ad75..d8ab812349 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -1,4 +1,4 @@ -import { Slug } from "@opencode-ai/util/slug" +import { Slug } from "@opencode-ai/shared/util/slug" import path from "path" import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index 04f2610dfe..b4794ba5b1 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -5,7 +5,7 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/ import { Config } from "@/config/config" import { InstanceState } from "@/effect/instance-state" import { Flag } from "@/flag/flag" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { withTransientReadRetry } from "@/util/effect-http-client" import { Global } from "../global" import { Instance } from "../project/instance" diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 5a4c041196..05d7882757 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -20,13 +20,12 @@ import { Wildcard } from "@/util/wildcard" import { SessionID } from "@/session/schema" import { Auth } from "@/auth" import { Installation } from "@/installation" -import { makeRuntime } from "@/effect/run-service" +import { EffectBridge } from "@/effect/bridge" import * as Option from "effect/Option" import * as OtelTracer from "@effect/opentelemetry/Tracer" export namespace LLM { const log = Log.create({ service: "llm" }) - const perms = makeRuntime(Permission.Service, Permission.defaultLayer) export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX type Result = Awaited> @@ -57,369 +56,371 @@ export namespace LLM { export class Service extends Context.Service()("@opencode/LLM") {} - export const layer: Layer.Layer = - Layer.effect( - Service, - Effect.gen(function* () { - const auth = yield* Auth.Service - const config = yield* Config.Service - const provider = yield* Provider.Service - const plugin = yield* Plugin.Service + const live: Layer.Layer< + Service, + never, + Auth.Service | Config.Service | Provider.Service | Plugin.Service | Permission.Service + > = Layer.effect( + Service, + Effect.gen(function* () { + const auth = yield* Auth.Service + const config = yield* Config.Service + const provider = yield* Provider.Service + const plugin = yield* Plugin.Service + const perm = yield* Permission.Service - const run = Effect.fn("LLM.run")(function* (input: StreamRequest) { - const l = log - .clone() - .tag("providerID", input.model.providerID) - .tag("modelID", input.model.id) - .tag("sessionID", input.sessionID) - .tag("small", (input.small ?? false).toString()) - .tag("agent", input.agent.name) - .tag("mode", input.agent.mode) - l.info("stream", { - modelID: input.model.id, - providerID: input.model.providerID, - }) - - const [language, cfg, item, info] = yield* Effect.all( - [ - provider.getLanguage(input.model), - config.get(), - provider.getProvider(input.model.providerID), - auth.get(input.model.providerID), - ], - { concurrency: "unbounded" }, - ) - - // TODO: move this to a proper hook - const isOpenaiOauth = item.id === "openai" && info?.type === "oauth" - - const system: string[] = [] - system.push( - [ - // use agent prompt otherwise provider prompt - ...(input.agent.prompt ? [input.agent.prompt] : SystemPrompt.provider(input.model)), - // any custom prompt passed into this call - ...input.system, - // any custom prompt from last user message - ...(input.user.system ? [input.user.system] : []), - ] - .filter((x) => x) - .join("\n"), - ) - - const header = system[0] - yield* plugin.trigger( - "experimental.chat.system.transform", - { sessionID: input.sessionID, model: input.model }, - { system }, - ) - // rejoin to maintain 2-part structure for caching if header unchanged - if (system.length > 2 && system[0] === header) { - const rest = system.slice(1) - system.length = 0 - system.push(header, rest.join("\n")) - } - - const variant = - !input.small && input.model.variants && input.user.model.variant - ? input.model.variants[input.user.model.variant] - : {} - const base = input.small - ? ProviderTransform.smallOptions(input.model) - : ProviderTransform.options({ - model: input.model, - sessionID: input.sessionID, - providerOptions: item.options, - }) - const options: Record = pipe( - base, - mergeDeep(input.model.options), - mergeDeep(input.agent.options), - mergeDeep(variant), - ) - if (isOpenaiOauth) { - options.instructions = system.join("\n") - } - - const isWorkflow = language instanceof GitLabWorkflowLanguageModel - const messages = isOpenaiOauth - ? input.messages - : isWorkflow - ? input.messages - : [ - ...system.map( - (x): ModelMessage => ({ - role: "system", - content: x, - }), - ), - ...input.messages, - ] - - const params = yield* plugin.trigger( - "chat.params", - { - sessionID: input.sessionID, - agent: input.agent.name, - model: input.model, - provider: item, - message: input.user, - }, - { - temperature: input.model.capabilities.temperature - ? (input.agent.temperature ?? ProviderTransform.temperature(input.model)) - : undefined, - topP: input.agent.topP ?? ProviderTransform.topP(input.model), - topK: ProviderTransform.topK(input.model), - maxOutputTokens: ProviderTransform.maxOutputTokens(input.model), - options, - }, - ) - - const { headers } = yield* plugin.trigger( - "chat.headers", - { - sessionID: input.sessionID, - agent: input.agent.name, - model: input.model, - provider: item, - message: input.user, - }, - { - headers: {}, - }, - ) - - const tools = resolveTools(input) - - // LiteLLM and some Anthropic proxies require the tools parameter to be present - // when message history contains tool calls, even if no tools are being used. - // Add a dummy tool that is never called to satisfy this validation. - // This is enabled for: - // 1. Providers with "litellm" in their ID or API ID (auto-detected) - // 2. Providers with explicit "litellmProxy: true" option (opt-in for custom gateways) - const isLiteLLMProxy = - item.options?.["litellmProxy"] === true || - input.model.providerID.toLowerCase().includes("litellm") || - input.model.api.id.toLowerCase().includes("litellm") - - // LiteLLM/Bedrock rejects requests where the message history contains tool - // calls but no tools param is present. When there are no active tools (e.g. - // during compaction), inject a stub tool to satisfy the validation requirement. - // The stub description explicitly tells the model not to call it. - if ( - (isLiteLLMProxy || input.model.providerID.includes("github-copilot")) && - Object.keys(tools).length === 0 && - hasToolCalls(input.messages) - ) { - tools["_noop"] = tool({ - description: "Do not call this tool. It exists only for API compatibility and must never be invoked.", - inputSchema: jsonSchema({ - type: "object", - properties: { - reason: { type: "string", description: "Unused" }, - }, - }), - execute: async () => ({ output: "", title: "", metadata: {} }), - }) - } - - // Wire up toolExecutor for DWS workflow models so that tool calls - // from the workflow service are executed via opencode's tool system - // and results sent back over the WebSocket. - if (language instanceof GitLabWorkflowLanguageModel) { - const workflowModel = language as GitLabWorkflowLanguageModel & { - sessionID?: string - sessionPreapprovedTools?: string[] - approvalHandler?: (approvalTools: { name: string; args: string }[]) => Promise<{ approved: boolean }> - } - workflowModel.sessionID = input.sessionID - workflowModel.systemPrompt = system.join("\n") - workflowModel.toolExecutor = async (toolName, argsJson, _requestID) => { - const t = tools[toolName] - if (!t || !t.execute) { - return { result: "", error: `Unknown tool: ${toolName}` } - } - try { - const result = await t.execute!(JSON.parse(argsJson), { - toolCallId: _requestID, - messages: input.messages, - abortSignal: input.abort, - }) - const output = typeof result === "string" ? result : (result?.output ?? JSON.stringify(result)) - return { - result: output, - metadata: typeof result === "object" ? result?.metadata : undefined, - title: typeof result === "object" ? result?.title : undefined, - } - } catch (e: any) { - return { result: "", error: e.message ?? String(e) } - } - } - - const ruleset = Permission.merge(input.agent.permission ?? [], input.permission ?? []) - workflowModel.sessionPreapprovedTools = Object.keys(tools).filter((name) => { - const match = ruleset.findLast((rule) => Wildcard.match(name, rule.permission)) - return !match || match.action !== "ask" - }) - - const approvedToolsForSession = new Set() - workflowModel.approvalHandler = Instance.bind(async (approvalTools) => { - const uniqueNames = [...new Set(approvalTools.map((t: { name: string }) => t.name))] as string[] - // Auto-approve tools that were already approved in this session - // (prevents infinite approval loops for server-side MCP tools) - if (uniqueNames.every((name) => approvedToolsForSession.has(name))) { - return { approved: true } - } - - const id = PermissionID.ascending() - let reply: Permission.Reply | undefined - let unsub: (() => void) | undefined - try { - unsub = Bus.subscribe(Permission.Event.Replied, (evt) => { - if (evt.properties.requestID === id) reply = evt.properties.reply - }) - const toolPatterns = approvalTools.map((t: { name: string; args: string }) => { - try { - const parsed = JSON.parse(t.args) as Record - const title = (parsed?.title ?? parsed?.name ?? "") as string - return title ? `${t.name}: ${title}` : t.name - } catch { - return t.name - } - }) - const uniquePatterns = [...new Set(toolPatterns)] as string[] - await perms.runPromise((svc) => - svc.ask({ - id, - sessionID: SessionID.make(input.sessionID), - permission: "workflow_tool_approval", - patterns: uniquePatterns, - metadata: { tools: approvalTools }, - always: uniquePatterns, - ruleset: [], - }), - ) - for (const name of uniqueNames) approvedToolsForSession.add(name) - workflowModel.sessionPreapprovedTools = [ - ...(workflowModel.sessionPreapprovedTools ?? []), - ...uniqueNames, - ] - return { approved: true } - } catch { - return { approved: false } - } finally { - unsub?.() - } - }) - } - - const tracer = cfg.experimental?.openTelemetry - ? Option.getOrUndefined(yield* Effect.serviceOption(OtelTracer.OtelTracer)) - : undefined - - return streamText({ - onError(error) { - l.error("stream error", { - error, - }) - }, - async experimental_repairToolCall(failed) { - const lower = failed.toolCall.toolName.toLowerCase() - if (lower !== failed.toolCall.toolName && tools[lower]) { - l.info("repairing tool call", { - tool: failed.toolCall.toolName, - repaired: lower, - }) - return { - ...failed.toolCall, - toolName: lower, - } - } - return { - ...failed.toolCall, - input: JSON.stringify({ - tool: failed.toolCall.toolName, - error: failed.error.message, - }), - toolName: "invalid", - } - }, - temperature: params.temperature, - topP: params.topP, - topK: params.topK, - providerOptions: ProviderTransform.providerOptions(input.model, params.options), - activeTools: Object.keys(tools).filter((x) => x !== "invalid"), - tools, - toolChoice: input.toolChoice, - maxOutputTokens: params.maxOutputTokens, - abortSignal: input.abort, - headers: { - ...(input.model.providerID.startsWith("opencode") - ? { - "x-opencode-project": Instance.project.id, - "x-opencode-session": input.sessionID, - "x-opencode-request": input.user.id, - "x-opencode-client": Flag.OPENCODE_CLIENT, - } - : { - "x-session-affinity": input.sessionID, - ...(input.parentSessionID ? { "x-parent-session-id": input.parentSessionID } : {}), - "User-Agent": `opencode/${Installation.VERSION}`, - }), - ...input.model.headers, - ...headers, - }, - maxRetries: input.retries ?? 0, - messages, - model: wrapLanguageModel({ - model: language, - middleware: [ - { - specificationVersion: "v3" as const, - async transformParams(args) { - if (args.type === "stream") { - // @ts-expect-error - args.params.prompt = ProviderTransform.message(args.params.prompt, input.model, options) - } - return args.params - }, - }, - ], - }), - experimental_telemetry: { - isEnabled: cfg.experimental?.openTelemetry, - functionId: "session.llm", - tracer, - metadata: { - userId: cfg.username ?? "unknown", - sessionId: input.sessionID, - }, - }, - }) + const run = Effect.fn("LLM.run")(function* (input: StreamRequest) { + const l = log + .clone() + .tag("providerID", input.model.providerID) + .tag("modelID", input.model.id) + .tag("sessionID", input.sessionID) + .tag("small", (input.small ?? false).toString()) + .tag("agent", input.agent.name) + .tag("mode", input.agent.mode) + l.info("stream", { + modelID: input.model.id, + providerID: input.model.providerID, }) - const stream: Interface["stream"] = (input) => - Stream.scoped( - Stream.unwrap( - Effect.gen(function* () { - const ctrl = yield* Effect.acquireRelease( - Effect.sync(() => new AbortController()), - (ctrl) => Effect.sync(() => ctrl.abort()), - ) + const [language, cfg, item, info] = yield* Effect.all( + [ + provider.getLanguage(input.model), + config.get(), + provider.getProvider(input.model.providerID), + auth.get(input.model.providerID), + ], + { concurrency: "unbounded" }, + ) - const result = yield* run({ ...input, abort: ctrl.signal }) + // TODO: move this to a proper hook + const isOpenaiOauth = item.id === "openai" && info?.type === "oauth" - return Stream.fromAsyncIterable(result.fullStream, (e) => - e instanceof Error ? e : new Error(String(e)), - ) + const system: string[] = [] + system.push( + [ + // use agent prompt otherwise provider prompt + ...(input.agent.prompt ? [input.agent.prompt] : SystemPrompt.provider(input.model)), + // any custom prompt passed into this call + ...input.system, + // any custom prompt from last user message + ...(input.user.system ? [input.user.system] : []), + ] + .filter((x) => x) + .join("\n"), + ) + + const header = system[0] + yield* plugin.trigger( + "experimental.chat.system.transform", + { sessionID: input.sessionID, model: input.model }, + { system }, + ) + // rejoin to maintain 2-part structure for caching if header unchanged + if (system.length > 2 && system[0] === header) { + const rest = system.slice(1) + system.length = 0 + system.push(header, rest.join("\n")) + } + + const variant = + !input.small && input.model.variants && input.user.model.variant + ? input.model.variants[input.user.model.variant] + : {} + const base = input.small + ? ProviderTransform.smallOptions(input.model) + : ProviderTransform.options({ + model: input.model, + sessionID: input.sessionID, + providerOptions: item.options, + }) + const options: Record = pipe( + base, + mergeDeep(input.model.options), + mergeDeep(input.agent.options), + mergeDeep(variant), + ) + if (isOpenaiOauth) { + options.instructions = system.join("\n") + } + + const isWorkflow = language instanceof GitLabWorkflowLanguageModel + const messages = isOpenaiOauth + ? input.messages + : isWorkflow + ? input.messages + : [ + ...system.map( + (x): ModelMessage => ({ + role: "system", + content: x, + }), + ), + ...input.messages, + ] + + const params = yield* plugin.trigger( + "chat.params", + { + sessionID: input.sessionID, + agent: input.agent.name, + model: input.model, + provider: item, + message: input.user, + }, + { + temperature: input.model.capabilities.temperature + ? (input.agent.temperature ?? ProviderTransform.temperature(input.model)) + : undefined, + topP: input.agent.topP ?? ProviderTransform.topP(input.model), + topK: ProviderTransform.topK(input.model), + maxOutputTokens: ProviderTransform.maxOutputTokens(input.model), + options, + }, + ) + + const { headers } = yield* plugin.trigger( + "chat.headers", + { + sessionID: input.sessionID, + agent: input.agent.name, + model: input.model, + provider: item, + message: input.user, + }, + { + headers: {}, + }, + ) + + const tools = resolveTools(input) + + // LiteLLM and some Anthropic proxies require the tools parameter to be present + // when message history contains tool calls, even if no tools are being used. + // Add a dummy tool that is never called to satisfy this validation. + // This is enabled for: + // 1. Providers with "litellm" in their ID or API ID (auto-detected) + // 2. Providers with explicit "litellmProxy: true" option (opt-in for custom gateways) + const isLiteLLMProxy = + item.options?.["litellmProxy"] === true || + input.model.providerID.toLowerCase().includes("litellm") || + input.model.api.id.toLowerCase().includes("litellm") + + // LiteLLM/Bedrock rejects requests where the message history contains tool + // calls but no tools param is present. When there are no active tools (e.g. + // during compaction), inject a stub tool to satisfy the validation requirement. + // The stub description explicitly tells the model not to call it. + if ( + (isLiteLLMProxy || input.model.providerID.includes("github-copilot")) && + Object.keys(tools).length === 0 && + hasToolCalls(input.messages) + ) { + tools["_noop"] = tool({ + description: "Do not call this tool. It exists only for API compatibility and must never be invoked.", + inputSchema: jsonSchema({ + type: "object", + properties: { + reason: { type: "string", description: "Unused" }, + }, + }), + execute: async () => ({ output: "", title: "", metadata: {} }), + }) + } + + // Wire up toolExecutor for DWS workflow models so that tool calls + // from the workflow service are executed via opencode's tool system + // and results sent back over the WebSocket. + if (language instanceof GitLabWorkflowLanguageModel) { + const workflowModel = language as GitLabWorkflowLanguageModel & { + sessionID?: string + sessionPreapprovedTools?: string[] + approvalHandler?: (approvalTools: { name: string; args: string }[]) => Promise<{ approved: boolean }> + } + workflowModel.sessionID = input.sessionID + workflowModel.systemPrompt = system.join("\n") + workflowModel.toolExecutor = async (toolName, argsJson, _requestID) => { + const t = tools[toolName] + if (!t || !t.execute) { + return { result: "", error: `Unknown tool: ${toolName}` } + } + try { + const result = await t.execute!(JSON.parse(argsJson), { + toolCallId: _requestID, + messages: input.messages, + abortSignal: input.abort, + }) + const output = typeof result === "string" ? result : (result?.output ?? JSON.stringify(result)) + return { + result: output, + metadata: typeof result === "object" ? result?.metadata : undefined, + title: typeof result === "object" ? result?.title : undefined, + } + } catch (e: any) { + return { result: "", error: e.message ?? String(e) } + } + } + + const ruleset = Permission.merge(input.agent.permission ?? [], input.permission ?? []) + workflowModel.sessionPreapprovedTools = Object.keys(tools).filter((name) => { + const match = ruleset.findLast((rule) => Wildcard.match(name, rule.permission)) + return !match || match.action !== "ask" + }) + + const bridge = yield* EffectBridge.make() + const approvedToolsForSession = new Set() + workflowModel.approvalHandler = Instance.bind(async (approvalTools) => { + const uniqueNames = [...new Set(approvalTools.map((t: { name: string }) => t.name))] as string[] + // Auto-approve tools that were already approved in this session + // (prevents infinite approval loops for server-side MCP tools) + if (uniqueNames.every((name) => approvedToolsForSession.has(name))) { + return { approved: true } + } + + const id = PermissionID.ascending() + let reply: Permission.Reply | undefined + let unsub: (() => void) | undefined + try { + unsub = Bus.subscribe(Permission.Event.Replied, (evt) => { + if (evt.properties.requestID === id) reply = evt.properties.reply + }) + const toolPatterns = approvalTools.map((t: { name: string; args: string }) => { + try { + const parsed = JSON.parse(t.args) as Record + const title = (parsed?.title ?? parsed?.name ?? "") as string + return title ? `${t.name}: ${title}` : t.name + } catch { + return t.name + } + }) + const uniquePatterns = [...new Set(toolPatterns)] as string[] + await bridge.promise( + perm.ask({ + id, + sessionID: SessionID.make(input.sessionID), + permission: "workflow_tool_approval", + patterns: uniquePatterns, + metadata: { tools: approvalTools }, + always: uniquePatterns, + ruleset: [], + }), + ) + for (const name of uniqueNames) approvedToolsForSession.add(name) + workflowModel.sessionPreapprovedTools = [...(workflowModel.sessionPreapprovedTools ?? []), ...uniqueNames] + return { approved: true } + } catch { + return { approved: false } + } finally { + unsub?.() + } + }) + } + + const tracer = cfg.experimental?.openTelemetry + ? Option.getOrUndefined(yield* Effect.serviceOption(OtelTracer.OtelTracer)) + : undefined + + return streamText({ + onError(error) { + l.error("stream error", { + error, + }) + }, + async experimental_repairToolCall(failed) { + const lower = failed.toolCall.toolName.toLowerCase() + if (lower !== failed.toolCall.toolName && tools[lower]) { + l.info("repairing tool call", { + tool: failed.toolCall.toolName, + repaired: lower, + }) + return { + ...failed.toolCall, + toolName: lower, + } + } + return { + ...failed.toolCall, + input: JSON.stringify({ + tool: failed.toolCall.toolName, + error: failed.error.message, }), - ), - ) + toolName: "invalid", + } + }, + temperature: params.temperature, + topP: params.topP, + topK: params.topK, + providerOptions: ProviderTransform.providerOptions(input.model, params.options), + activeTools: Object.keys(tools).filter((x) => x !== "invalid"), + tools, + toolChoice: input.toolChoice, + maxOutputTokens: params.maxOutputTokens, + abortSignal: input.abort, + headers: { + ...(input.model.providerID.startsWith("opencode") + ? { + "x-opencode-project": Instance.project.id, + "x-opencode-session": input.sessionID, + "x-opencode-request": input.user.id, + "x-opencode-client": Flag.OPENCODE_CLIENT, + } + : { + "x-session-affinity": input.sessionID, + ...(input.parentSessionID ? { "x-parent-session-id": input.parentSessionID } : {}), + "User-Agent": `opencode/${Installation.VERSION}`, + }), + ...input.model.headers, + ...headers, + }, + maxRetries: input.retries ?? 0, + messages, + model: wrapLanguageModel({ + model: language, + middleware: [ + { + specificationVersion: "v3" as const, + async transformParams(args) { + if (args.type === "stream") { + // @ts-expect-error + args.params.prompt = ProviderTransform.message(args.params.prompt, input.model, options) + } + return args.params + }, + }, + ], + }), + experimental_telemetry: { + isEnabled: cfg.experimental?.openTelemetry, + functionId: "session.llm", + tracer, + metadata: { + userId: cfg.username ?? "unknown", + sessionId: input.sessionID, + }, + }, + }) + }) - return Service.of({ stream }) - }), - ) + const stream: Interface["stream"] = (input) => + Stream.scoped( + Stream.unwrap( + Effect.gen(function* () { + const ctrl = yield* Effect.acquireRelease( + Effect.sync(() => new AbortController()), + (ctrl) => Effect.sync(() => ctrl.abort()), + ) + + const result = yield* run({ ...input, abort: ctrl.signal }) + + return Stream.fromAsyncIterable(result.fullStream, (e) => (e instanceof Error ? e : new Error(String(e)))) + }), + ), + ) + + return Service.of({ stream }) + }), + ) + + export const layer = live.pipe(Layer.provide(Permission.defaultLayer)) export const defaultLayer = Layer.suspend(() => layer.pipe( diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 4c18d1f7e0..8c82d4d73f 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -1,7 +1,7 @@ import { BusEvent } from "@/bus/bus-event" import { SessionID, MessageID, PartID } from "./schema" import z from "zod" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai" import { LSP } from "../lsp" import { Snapshot } from "@/snapshot" diff --git a/packages/opencode/src/session/message.ts b/packages/opencode/src/session/message.ts index ee5eac08b6..396034825a 100644 --- a/packages/opencode/src/session/message.ts +++ b/packages/opencode/src/session/message.ts @@ -1,7 +1,7 @@ import z from "zod" import { SessionID } from "./schema" import { ModelID, ProviderID } from "../provider/schema" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" export namespace Message { export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({})) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 3efcc03657..ffd074d3f8 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -32,14 +32,14 @@ import { Command } from "../command" import { pathToFileURL, fileURLToPath } from "url" import { ConfigMarkdown } from "../config/markdown" import { SessionSummary } from "./summary" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { SessionProcessor } from "./processor" import { Tool } from "@/tool/tool" import { Permission } from "@/permission" import { SessionStatus } from "./status" import { LLM } from "./llm" import { Shell } from "@/shell/shell" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Truncate } from "@/tool/truncate" import { decodeDataUrl } from "@/util/data-url" import { Process } from "@/util/process" @@ -48,6 +48,7 @@ import { EffectLogger } from "@/effect/logger" import { InstanceState } from "@/effect/instance-state" import { TaskTool, type TaskPromptOps } from "@/tool/task" import { SessionRunState } from "./run-state" +import { EffectBridge } from "@/effect/bridge" // @ts-ignore globalThis.AI_SDK_LOG_WARNINGS = false @@ -105,11 +106,7 @@ export namespace SessionPrompt { const sys = yield* SystemPrompt.Service const llm = yield* LLM.Service const runner = Effect.fn("SessionPrompt.runner")(function* () { - const ctx = yield* Effect.context() - return { - promise: (effect: Effect.Effect) => Effect.runPromiseWith(ctx)(effect), - fork: (effect: Effect.Effect) => Effect.runForkWith(ctx)(effect), - } + return yield* EffectBridge.make() }) const ops = Effect.fn("SessionPrompt.ops")(function* () { const run = yield* runner() diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index 5ec9a585b0..39eb8cfb74 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -1,4 +1,4 @@ -import type { NamedError } from "@opencode-ai/util/error" +import type { NamedError } from "@opencode-ai/shared/util/error" import { Cause, Clock, Duration, Effect, Schedule } from "effect" import { MessageV2 } from "./message-v2" import { iife } from "@/util/iife" diff --git a/packages/opencode/src/skill/discovery.ts b/packages/opencode/src/skill/discovery.ts index 0bc3ee6290..0323f250f6 100644 --- a/packages/opencode/src/skill/discovery.ts +++ b/packages/opencode/src/skill/discovery.ts @@ -2,7 +2,7 @@ import { NodePath } from "@effect/platform-node" import { Effect, Layer, Path, Schema, Context } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { withTransientReadRetry } from "@/util/effect-http-client" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Global } from "../global" import { Log } from "../util/log" diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index 6c4f290a08..79b426c69c 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -3,17 +3,17 @@ import path from "path" import { pathToFileURL } from "url" import z from "zod" import { Effect, Layer, Context } from "effect" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import type { Agent } from "@/agent/agent" import { Bus } from "@/bus" import { InstanceState } from "@/effect/instance-state" import { Flag } from "@/flag/flag" import { Global } from "@/global" import { Permission } from "@/permission" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Config } from "../config/config" import { ConfigMarkdown } from "../config/markdown" -import { Glob } from "../util/glob" +import { Glob } from "@opencode-ai/shared/util/glob" import { Log } from "../util/log" import { Discovery } from "./discovery" diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index ef92ddcbf3..9378e309aa 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -5,8 +5,8 @@ import path from "path" import z from "zod" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" import { InstanceState } from "@/effect/instance-state" -import { AppFileSystem } from "@/filesystem" -import { Hash } from "@/util/hash" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { Hash } from "@opencode-ai/shared/util/hash" import { Config } from "../config/config" import { Global } from "../global" import { Log } from "../util/log" @@ -90,12 +90,19 @@ export namespace Snapshot { const args = (cmd: string[]) => ["--git-dir", state.gitdir, "--work-tree", state.worktree, ...cmd] + const enc = new TextEncoder() + const feed = (list: string[]) => Stream.make(enc.encode(list.join("\0") + "\0")) + const git = Effect.fnUntraced( - function* (cmd: string[], opts?: { cwd?: string; env?: Record }) { + function* ( + cmd: string[], + opts?: { cwd?: string; env?: Record; stdin?: ChildProcess.CommandInput }, + ) { const proc = ChildProcess.make("git", cmd, { cwd: opts?.cwd, env: opts?.env, extendEnv: true, + stdin: opts?.stdin, }) const handle = yield* spawner.spawn(proc) const [text, stderr] = yield* Effect.all( @@ -115,6 +122,59 @@ export namespace Snapshot { ), ) + const ignore = Effect.fnUntraced(function* (files: string[]) { + if (!files.length) return new Set() + const check = yield* git( + [ + ...quote, + "--git-dir", + path.join(state.worktree, ".git"), + "--work-tree", + state.worktree, + "check-ignore", + "--no-index", + "--stdin", + "-z", + ], + { + cwd: state.directory, + stdin: feed(files), + }, + ) + if (check.code !== 0 && check.code !== 1) return new Set() + return new Set(check.text.split("\0").filter(Boolean)) + }) + + const drop = Effect.fnUntraced(function* (files: string[]) { + if (!files.length) return + yield* git( + [ + ...cfg, + ...args(["rm", "--cached", "-f", "--ignore-unmatch", "--pathspec-from-file=-", "--pathspec-file-nul"]), + ], + { + cwd: state.directory, + stdin: feed(files), + }, + ) + }) + + const stage = Effect.fnUntraced(function* (files: string[]) { + if (!files.length) return + const result = yield* git( + [...cfg, ...args(["add", "--all", "--sparse", "--pathspec-from-file=-", "--pathspec-file-nul"])], + { + cwd: state.directory, + stdin: feed(files), + }, + ) + if (result.code === 0) return + log.warn("failed to add snapshot files", { + exitCode: result.code, + stderr: result.stderr, + }) + }) + const exists = (file: string) => fs.exists(file).pipe(Effect.orDie) const read = (file: string) => fs.readFileString(file).pipe(Effect.catch(() => Effect.succeed(""))) const remove = (file: string) => fs.remove(file).pipe(Effect.catch(() => Effect.void)) @@ -176,60 +236,41 @@ export namespace Snapshot { const all = Array.from(new Set([...tracked, ...untracked])) if (!all.length) return - // Filter out files that are now gitignored even if previously tracked - // Files may have been tracked before being gitignored, so we need to check - // against the source project's current gitignore rules - // Use --no-index to check purely against patterns (ignoring whether file is tracked) - const checkArgs = [ - ...quote, - "--git-dir", - path.join(state.worktree, ".git"), - "--work-tree", - state.worktree, - "check-ignore", - "--no-index", - "--", - ...all, - ] - const check = yield* git(checkArgs, { cwd: state.directory }) - const ignored = - check.code === 0 ? new Set(check.text.trim().split("\n").filter(Boolean)) : new Set() - const filtered = all.filter((item) => !ignored.has(item)) + // Resolve source-repo ignore rules against the exact candidate set. + // --no-index keeps this pattern-based even when a path is already tracked. + const ignored = yield* ignore(all) // Remove newly-ignored files from snapshot index to prevent re-adding if (ignored.size > 0) { const ignoredFiles = Array.from(ignored) log.info("removing gitignored files from snapshot", { count: ignoredFiles.length }) - yield* git([...cfg, ...args(["rm", "--cached", "-f", "--", ...ignoredFiles])], { - cwd: state.directory, - }) + yield* drop(ignoredFiles) } - if (!filtered.length) return + const allow = all.filter((item) => !ignored.has(item)) + if (!allow.length) return - const large = (yield* Effect.all( - filtered.map((item) => - fs - .stat(path.join(state.directory, item)) - .pipe(Effect.catch(() => Effect.void)) - .pipe( - Effect.map((stat) => { - if (!stat || stat.type !== "File") return - const size = typeof stat.size === "bigint" ? Number(stat.size) : stat.size - return size > limit ? item : undefined - }), - ), - ), - { concurrency: 8 }, - )).filter((item): item is string => Boolean(item)) - yield* sync(large) - const result = yield* git([...cfg, ...args(["add", "--sparse", "."])], { cwd: state.directory }) - if (result.code !== 0) { - log.warn("failed to add snapshot files", { - exitCode: result.code, - stderr: result.stderr, - }) - } + const large = new Set( + (yield* Effect.all( + allow.map((item) => + fs + .stat(path.join(state.directory, item)) + .pipe(Effect.catch(() => Effect.void)) + .pipe( + Effect.map((stat) => { + if (!stat || stat.type !== "File") return + const size = typeof stat.size === "bigint" ? Number(stat.size) : stat.size + return size > limit ? item : undefined + }), + ), + ), + { concurrency: 8 }, + )).filter((item): item is string => Boolean(item)), + ) + const block = new Set(untracked.filter((item) => large.has(item))) + yield* sync(Array.from(block)) + // Stage only the allowed candidate paths so snapshot updates stay scoped. + yield* stage(allow.filter((item) => !block.has(item))) }) const cleanup = Effect.fnUntraced(function* () { @@ -295,33 +336,14 @@ export namespace Snapshot { .map((x) => x.trim()) .filter(Boolean) - // Filter out files that are now gitignored - if (files.length > 0) { - const checkArgs = [ - ...quote, - "--git-dir", - path.join(state.worktree, ".git"), - "--work-tree", - state.worktree, - "check-ignore", - "--no-index", - "--", - ...files, - ] - const check = yield* git(checkArgs, { cwd: state.directory }) - if (check.code === 0) { - const ignored = new Set(check.text.trim().split("\n").filter(Boolean)) - const filtered = files.filter((item) => !ignored.has(item)) - return { - hash, - files: filtered.map((x) => path.join(state.worktree, x).replaceAll("\\", "/")), - } - } - } + // Hide ignored-file removals from the user-facing patch output. + const ignored = yield* ignore(files) return { hash, - files: files.map((x) => path.join(state.worktree, x).replaceAll("\\", "/")), + files: files + .filter((item) => !ignored.has(item)) + .map((x) => path.join(state.worktree, x).replaceAll("\\", "/")), } }), ) @@ -672,27 +694,12 @@ export namespace Snapshot { ] }) - // Filter out files that are now gitignored - if (rows.length > 0) { - const files = rows.map((r) => r.file) - const checkArgs = [ - ...quote, - "--git-dir", - path.join(state.worktree, ".git"), - "--work-tree", - state.worktree, - "check-ignore", - "--no-index", - "--", - ...files, - ] - const check = yield* git(checkArgs, { cwd: state.directory }) - if (check.code === 0) { - const ignored = new Set(check.text.trim().split("\n").filter(Boolean)) - const filtered = rows.filter((r) => !ignored.has(r.file)) - rows.length = 0 - rows.push(...filtered) - } + // Hide ignored-file removals from the user-facing diff output. + const ignored = yield* ignore(rows.map((r) => r.file)) + if (ignored.size > 0) { + const filtered = rows.filter((r) => !ignored.has(r.file)) + rows.length = 0 + rows.push(...filtered) } const step = 100 diff --git a/packages/opencode/src/storage/db.ts b/packages/opencode/src/storage/db.ts index a7dbf93800..68a41e471f 100644 --- a/packages/opencode/src/storage/db.ts +++ b/packages/opencode/src/storage/db.ts @@ -6,7 +6,7 @@ import { LocalContext } from "../util/local-context" import { lazy } from "../util/lazy" import { Global } from "../global" import { Log } from "../util/log" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import z from "zod" import path from "path" import { readFileSync, readdirSync, existsSync } from "fs" diff --git a/packages/opencode/src/storage/json-migration.ts b/packages/opencode/src/storage/json-migration.ts index 400e3dc9ef..89d27b9a7b 100644 --- a/packages/opencode/src/storage/json-migration.ts +++ b/packages/opencode/src/storage/json-migration.ts @@ -8,7 +8,7 @@ import { SessionShareTable } from "../share/share.sql" import path from "path" import { existsSync } from "fs" import { Filesystem } from "../util/filesystem" -import { Glob } from "../util/glob" +import { Glob } from "@opencode-ai/shared/util/glob" export namespace JsonMigration { const log = Log.create({ service: "json-migration" }) diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts index a123cd664f..359c750ced 100644 --- a/packages/opencode/src/storage/storage.ts +++ b/packages/opencode/src/storage/storage.ts @@ -1,9 +1,9 @@ import { Log } from "../util/log" import path from "path" import { Global } from "../global" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import z from "zod" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Effect, Exit, Layer, Option, RcMap, Schema, Context, TxReentrantLock } from "effect" import { Git } from "@/git" diff --git a/packages/opencode/src/sync/index.ts b/packages/opencode/src/sync/index.ts index d7cb7f774f..ce598dae67 100644 --- a/packages/opencode/src/sync/index.ts +++ b/packages/opencode/src/sync/index.ts @@ -199,6 +199,25 @@ export namespace SyncEvent { process(def, event, { publish: !!options?.publish }) } + export function replayAll(events: SerializedEvent[], options?: { publish: boolean }) { + const source = events[0]?.aggregateID + if (!source) return + if (events.some((item) => item.aggregateID !== source)) { + throw new Error("Replay events must belong to the same session") + } + const start = events[0].seq + for (const [i, item] of events.entries()) { + const seq = start + i + if (item.seq !== seq) { + throw new Error(`Replay sequence mismatch at index ${i}: expected ${seq}, got ${item.seq}`) + } + } + for (const item of events) { + replay(item, options) + } + return source + } + export function run(def: Def, data: Event["data"], options?: { publish?: boolean }) { const agg = (data as Record)[def.aggregate] // This should never happen: we've enforced it via typescript in diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts index fd38a9b224..b9877d8fec 100644 --- a/packages/opencode/src/tool/apply_patch.ts +++ b/packages/opencode/src/tool/apply_patch.ts @@ -10,7 +10,7 @@ import { createTwoFilesPatch, diffLines } from "diff" import { assertExternalDirectoryEffect } from "./external-directory" import { trimDiff } from "./edit" import { LSP } from "../lsp" -import { AppFileSystem } from "../filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import DESCRIPTION from "./apply_patch.txt" import { File } from "../file" import { Format } from "../format" diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index ad6739b9a9..3bb936944c 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -8,7 +8,7 @@ import { Instance } from "../project/instance" import { lazy } from "@/util/lazy" import { Language, type Node } from "web-tree-sitter" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { fileURLToPath } from "url" import { Flag } from "@/flag/flag" import { Shell } from "@/shell/shell" diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index a835714c69..bc8478e39f 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -19,7 +19,7 @@ import { Filesystem } from "../util/filesystem" import { Instance } from "../project/instance" import { Snapshot } from "@/snapshot" import { assertExternalDirectoryEffect } from "./external-directory" -import { AppFileSystem } from "../filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" function normalizeLineEndings(text: string): string { return text.replaceAll("\r\n", "\n") diff --git a/packages/opencode/src/tool/external-directory.ts b/packages/opencode/src/tool/external-directory.ts index 9df3e0aaf5..352cc07390 100644 --- a/packages/opencode/src/tool/external-directory.ts +++ b/packages/opencode/src/tool/external-directory.ts @@ -4,7 +4,7 @@ import { EffectLogger } from "@/effect/logger" import { InstanceState } from "@/effect/instance-state" import type { Tool } from "./tool" import { Instance } from "../project/instance" -import { AppFileSystem } from "../filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" type Kind = "file" | "directory" diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts index c1577bc7d6..778a74ddcf 100644 --- a/packages/opencode/src/tool/glob.ts +++ b/packages/opencode/src/tool/glob.ts @@ -3,7 +3,7 @@ import z from "zod" import { Effect, Option } from "effect" import * as Stream from "effect/Stream" import { InstanceState } from "@/effect/instance-state" -import { AppFileSystem } from "../filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Ripgrep } from "../file/ripgrep" import { assertExternalDirectoryEffect } from "./external-directory" import DESCRIPTION from "./glob.txt" diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts index 0d717ba372..9a2bab5b2d 100644 --- a/packages/opencode/src/tool/grep.ts +++ b/packages/opencode/src/tool/grep.ts @@ -2,7 +2,7 @@ import path from "path" import z from "zod" import { Effect, Option } from "effect" import { InstanceState } from "@/effect/instance-state" -import { AppFileSystem } from "../filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Ripgrep } from "../file/ripgrep" import { assertExternalDirectoryEffect } from "./external-directory" import DESCRIPTION from "./grep.txt" diff --git a/packages/opencode/src/tool/lsp.ts b/packages/opencode/src/tool/lsp.ts index c5a5d6f819..36cab3c1c3 100644 --- a/packages/opencode/src/tool/lsp.ts +++ b/packages/opencode/src/tool/lsp.ts @@ -7,7 +7,7 @@ import DESCRIPTION from "./lsp.txt" import { Instance } from "../project/instance" import { pathToFileURL } from "url" import { assertExternalDirectoryEffect } from "./external-directory" -import { AppFileSystem } from "../filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" const operations = [ "goToDefinition", diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index 501a8c97ed..701bfc4b9d 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -5,7 +5,7 @@ import { open } from "fs/promises" import * as path from "path" import { createInterface } from "readline" import { Tool } from "./tool" -import { AppFileSystem } from "../filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { LSP } from "../lsp" import { FileTime } from "../file/time" import DESCRIPTION from "./read.txt" diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 2e7da5b506..6900feecc3 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -26,7 +26,7 @@ import { Log } from "@/util/log" import { LspTool } from "./lsp" import { Truncate } from "./truncate" import { ApplyPatchTool } from "./apply_patch" -import { Glob } from "../util/glob" +import { Glob } from "@opencode-ai/shared/util/glob" import path from "path" import { pathToFileURL } from "url" import { Effect, Layer, Context } from "effect" @@ -41,7 +41,7 @@ import { Todo } from "../session/todo" import { LSP } from "../lsp" import { FileTime } from "../file/time" import { Instruction } from "../session/instruction" -import { AppFileSystem } from "../filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Bus } from "../bus" import { Agent } from "../agent/agent" import { Skill } from "../skill" diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 49dd2b0605..30be63a320 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -80,8 +80,14 @@ export namespace Tool { Effect.gen(function* () { const toolInfo = init instanceof Function ? { ...(yield* init()) } : { ...init } const execute = toolInfo.execute - toolInfo.execute = (args, ctx) => - Effect.gen(function* () { + toolInfo.execute = (args, ctx) => { + const attrs = { + "tool.name": id, + "session.id": ctx.sessionID, + "message.id": ctx.messageID, + ...(ctx.callID ? { "tool.call_id": ctx.callID } : {}), + } + return Effect.gen(function* () { yield* Effect.try({ try: () => toolInfo.parameters.parse(args), catch: (error) => { @@ -109,7 +115,8 @@ export namespace Tool { ...(truncated.truncated && { outputPath: truncated.outputPath }), }, } - }).pipe(Effect.orDie) + }).pipe(Effect.orDie, Effect.withSpan("Tool.execute", { attributes: attrs })) + } return toolInfo }) } diff --git a/packages/opencode/src/tool/truncate.ts b/packages/opencode/src/tool/truncate.ts index e6bab1a16b..a7bd8a4b16 100644 --- a/packages/opencode/src/tool/truncate.ts +++ b/packages/opencode/src/tool/truncate.ts @@ -2,7 +2,7 @@ import { NodePath } from "@effect/platform-node" import { Cause, Duration, Effect, Layer, Schedule, Context } from "effect" import path from "path" import type { Agent } from "../agent/agent" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { evaluate } from "@/permission/evaluate" import { Identifier } from "../id/id" import { Log } from "../util/log" diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 7a9d82cf8b..337c2708c9 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -10,7 +10,7 @@ import { File } from "../file" import { FileWatcher } from "../file/watcher" import { Format } from "../format" import { FileTime } from "../file/time" -import { AppFileSystem } from "../filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Instance } from "../project/instance" import { trimDiff } from "./edit" import { assertExternalDirectoryEffect } from "./external-directory" diff --git a/packages/opencode/src/util/filesystem.ts b/packages/opencode/src/util/filesystem.ts index 5f50231b03..b4aef05456 100644 --- a/packages/opencode/src/util/filesystem.ts +++ b/packages/opencode/src/util/filesystem.ts @@ -5,7 +5,7 @@ import { realpathSync } from "fs" import { dirname, join, relative, resolve as pathResolve, win32 } from "path" import { Readable } from "stream" import { pipeline } from "stream/promises" -import { Glob } from "./glob" +import { Glob } from "@opencode-ai/shared/util/glob" export namespace Filesystem { // Fast sync version for metadata checks diff --git a/packages/opencode/src/util/log.ts b/packages/opencode/src/util/log.ts index f94e9866f7..7812632768 100644 --- a/packages/opencode/src/util/log.ts +++ b/packages/opencode/src/util/log.ts @@ -3,7 +3,7 @@ import fs from "fs/promises" import { createWriteStream } from "fs" import { Global } from "../global" import z from "zod" -import { Glob } from "./glob" +import { Glob } from "@opencode-ai/shared/util/glob" export namespace Log { export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" }) diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index 18240524a1..14a3a0dc9b 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -1,5 +1,5 @@ import z from "zod" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { Global } from "../global" import { Instance } from "../project/instance" import { InstanceBootstrap } from "../project/bootstrap" @@ -8,7 +8,7 @@ import { Database, eq } from "../storage/db" import { ProjectTable } from "../project/project.sql" import type { ProjectID } from "../project/schema" import { Log } from "../util/log" -import { Slug } from "@opencode-ai/util/slug" +import { Slug } from "@opencode-ai/shared/util/slug" import { errorMessage } from "../util/error" import { BusEvent } from "@/bus/bus-event" import { GlobalBus } from "@/bus/global" @@ -16,7 +16,7 @@ import { Git } from "@/git" import { Effect, Layer, Path, Scope, Context, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" -import { AppFileSystem } from "@/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { BootstrapRuntime } from "@/effect/bootstrap-runtime" import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner" import { InstanceState } from "@/effect/instance-state" diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index e759985feb..ed7e689da4 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -5,7 +5,7 @@ import { Config } from "../../src/config/config" import { Instance } from "../../src/project/instance" import { Auth } from "../../src/auth" import { AccessToken, Account, AccountID, OrgID } from "../../src/account" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Env } from "../../src/env" import { provideTmpdirInstance } from "../fixture/fixture" import { tmpdir, tmpdirScoped } from "../fixture/fixture" diff --git a/packages/opencode/test/effect/app-runtime-logger.test.ts b/packages/opencode/test/effect/app-runtime-logger.test.ts index 8a7aab6cf8..7388748f92 100644 --- a/packages/opencode/test/effect/app-runtime-logger.test.ts +++ b/packages/opencode/test/effect/app-runtime-logger.test.ts @@ -1,6 +1,7 @@ import { expect, test } from "bun:test" import { Context, Effect, Layer, Logger } from "effect" import { AppRuntime } from "../../src/effect/app-runtime" +import { EffectBridge } from "../../src/effect/bridge" import { InstanceRef } from "../../src/effect/instance-ref" import { EffectLogger } from "../../src/effect/logger" import { makeRuntime } from "../../src/effect/run-service" @@ -59,3 +60,33 @@ test("AppRuntime attaches InstanceRef from ALS", async () => { expect(dir).toBe(tmp.path) }) + +test("EffectBridge preserves logger and instance context across async boundaries", async () => { + await using tmp = await tmpdir({ git: true }) + + const result = await Instance.provide({ + directory: tmp.path, + fn: () => + AppRuntime.runPromise( + Effect.gen(function* () { + const bridge = yield* EffectBridge.make() + return yield* Effect.promise(() => + Promise.resolve().then(() => + bridge.promise( + Effect.gen(function* () { + return { + directory: (yield* InstanceRef)?.directory, + ...check(yield* Effect.service(Logger.CurrentLoggers)), + } + }), + ), + ), + ) + }), + ), + }) + + expect(result.directory).toBe(tmp.path) + expect(result.effectLogger).toBe(true) + expect(result.defaultLogger).toBe(false) +}) diff --git a/packages/opencode/test/filesystem/filesystem.test.ts b/packages/opencode/test/filesystem/filesystem.test.ts index ca73b3336b..0bb4ba5839 100644 --- a/packages/opencode/test/filesystem/filesystem.test.ts +++ b/packages/opencode/test/filesystem/filesystem.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect } from "bun:test" import { Effect, Layer } from "effect" import { NodeFileSystem } from "@effect/platform-node" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { testEffect } from "../lib/effect" import path from "path" diff --git a/packages/opencode/test/fixture/flock-worker.ts b/packages/opencode/test/fixture/flock-worker.ts index ac05fe810c..9954d290cc 100644 --- a/packages/opencode/test/fixture/flock-worker.ts +++ b/packages/opencode/test/fixture/flock-worker.ts @@ -1,5 +1,5 @@ import fs from "fs/promises" -import { Flock } from "../../src/util/flock" +import { Flock } from "@opencode-ai/shared/util/flock" type Msg = { key: string diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index eddd79bc6f..ba253a9205 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -9,7 +9,7 @@ import { ProjectID } from "../../src/project/schema" import { Effect, Layer, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" Log.init({ print: false }) diff --git a/packages/opencode/test/provider/amazon-bedrock.test.ts b/packages/opencode/test/provider/amazon-bedrock.test.ts index 712f36086f..6783ff5889 100644 --- a/packages/opencode/test/provider/amazon-bedrock.test.ts +++ b/packages/opencode/test/provider/amazon-bedrock.test.ts @@ -11,6 +11,10 @@ import { Global } from "../../src/global" import { Filesystem } from "../../src/util/filesystem" import { Effect } from "effect" import { AppRuntime } from "../../src/effect/app-runtime" +import { makeRuntime } from "../../src/effect/run-service" + +const env = makeRuntime(Env.Service, Env.defaultLayer) +const set = (k: string, v: string) => env.runSync((svc) => svc.set(k, v)) async function list() { return AppRuntime.runPromise( @@ -42,8 +46,8 @@ test("Bedrock: config region takes precedence over AWS_REGION env var", async () await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_REGION", "us-east-1") - Env.set("AWS_PROFILE", "default") + set("AWS_REGION", "us-east-1") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -67,8 +71,8 @@ test("Bedrock: falls back to AWS_REGION env var when no config region", async () await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_REGION", "eu-west-1") - Env.set("AWS_PROFILE", "default") + set("AWS_REGION", "eu-west-1") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -122,9 +126,9 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "") - Env.set("AWS_ACCESS_KEY_ID", "") - Env.set("AWS_BEARER_TOKEN_BEDROCK", "") + set("AWS_PROFILE", "") + set("AWS_ACCESS_KEY_ID", "") + set("AWS_BEARER_TOKEN_BEDROCK", "") }, fn: async () => { const providers = await list() @@ -168,8 +172,8 @@ test("Bedrock: config profile takes precedence over AWS_PROFILE env var", async await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") - Env.set("AWS_ACCESS_KEY_ID", "test-key-id") + set("AWS_PROFILE", "default") + set("AWS_ACCESS_KEY_ID", "test-key-id") }, fn: async () => { const providers = await list() @@ -200,7 +204,7 @@ test("Bedrock: includes custom endpoint in options when specified", async () => await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -233,10 +237,10 @@ test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async () await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token") - Env.set("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/my-eks-role") - Env.set("AWS_PROFILE", "") - Env.set("AWS_ACCESS_KEY_ID", "") + set("AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token") + set("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/my-eks-role") + set("AWS_PROFILE", "") + set("AWS_ACCESS_KEY_ID", "") }, fn: async () => { const providers = await list() @@ -276,7 +280,7 @@ test("Bedrock: model with us. prefix should not be double-prefixed", async () => await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -313,7 +317,7 @@ test("Bedrock: model with global. prefix should not be prefixed", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -349,7 +353,7 @@ test("Bedrock: model with eu. prefix should not be double-prefixed", async () => await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -385,7 +389,7 @@ test("Bedrock: model without prefix in US region should get us. prefix added", a await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index ac990bc0fb..dafa9dd822 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -13,6 +13,10 @@ import { Filesystem } from "../../src/util/filesystem" import { Env } from "../../src/env" import { Effect } from "effect" import { AppRuntime } from "../../src/effect/app-runtime" +import { makeRuntime } from "../../src/effect/run-service" + +const env = makeRuntime(Env.Service, Env.defaultLayer) +const set = (k: string, v: string) => env.runSync((svc) => svc.set(k, v)) async function run(fn: (provider: Provider.Interface) => Effect.Effect) { return AppRuntime.runPromise( @@ -71,7 +75,7 @@ test("provider loaded from env variable", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -126,7 +130,7 @@ test("disabled_providers excludes provider", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -150,8 +154,8 @@ test("enabled_providers restricts to only listed providers", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") - Env.set("OPENAI_API_KEY", "test-openai-key") + set("ANTHROPIC_API_KEY", "test-api-key") + set("OPENAI_API_KEY", "test-openai-key") }, fn: async () => { const providers = await list() @@ -180,7 +184,7 @@ test("model whitelist filters models for provider", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -211,7 +215,7 @@ test("model blacklist excludes specific models", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -246,7 +250,7 @@ test("custom model alias via config", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -322,7 +326,7 @@ test("env variable takes precedence, config merges options", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "env-api-key") + set("ANTHROPIC_API_KEY", "env-api-key") }, fn: async () => { const providers = await list() @@ -348,7 +352,7 @@ test("getModel returns model for valid provider/model", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514")) @@ -375,7 +379,7 @@ test("getModel throws ModelNotFoundError for invalid model", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { expect(getModel(ProviderID.anthropic, ModelID.make("nonexistent-model"))).rejects.toThrow() @@ -428,7 +432,7 @@ test("defaultModel returns first available model when no config set", async () = await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model = await defaultModel() @@ -453,7 +457,7 @@ test("defaultModel respects config model setting", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model = await defaultModel() @@ -568,7 +572,7 @@ test("model options are merged from existing model", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -597,7 +601,7 @@ test("provider removed when all models filtered out", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -620,7 +624,7 @@ test("closest finds model by partial match", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const result = await closest(ProviderID.anthropic, ["sonnet-4"]) @@ -675,7 +679,7 @@ test("getModel uses realIdByKey for aliased models", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -790,7 +794,7 @@ test("model inherits properties from existing database model", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -818,7 +822,7 @@ test("disabled_providers prevents loading even with env var", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("OPENAI_API_KEY", "test-openai-key") + set("OPENAI_API_KEY", "test-openai-key") }, fn: async () => { const providers = await list() @@ -842,8 +846,8 @@ test("enabled_providers with empty array allows no providers", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") - Env.set("OPENAI_API_KEY", "test-openai-key") + set("ANTHROPIC_API_KEY", "test-api-key") + set("OPENAI_API_KEY", "test-openai-key") }, fn: async () => { const providers = await list() @@ -872,7 +876,7 @@ test("whitelist and blacklist can be combined", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -981,7 +985,7 @@ test("getSmallModel returns appropriate small model", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model = await getSmallModel(ProviderID.anthropic) @@ -1006,7 +1010,7 @@ test("getSmallModel respects config small_model override", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model = await getSmallModel(ProviderID.anthropic) @@ -1054,8 +1058,8 @@ test("multiple providers can be configured simultaneously", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-anthropic-key") - Env.set("OPENAI_API_KEY", "test-openai-key") + set("ANTHROPIC_API_KEY", "test-anthropic-key") + set("OPENAI_API_KEY", "test-openai-key") }, fn: async () => { const providers = await list() @@ -1133,7 +1137,7 @@ test("model alias name defaults to alias key when id differs", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1173,7 +1177,7 @@ test("provider with multiple env var options only includes apiKey when single en await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("MULTI_ENV_KEY_1", "test-key") + set("MULTI_ENV_KEY_1", "test-key") }, fn: async () => { const providers = await list() @@ -1215,7 +1219,7 @@ test("provider with single env var includes apiKey automatically", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("SINGLE_ENV_KEY", "my-api-key") + set("SINGLE_ENV_KEY", "my-api-key") }, fn: async () => { const providers = await list() @@ -1252,7 +1256,7 @@ test("model cost overrides existing cost values", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1331,9 +1335,9 @@ test("disabled_providers and enabled_providers interaction", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-anthropic") - Env.set("OPENAI_API_KEY", "test-openai") - Env.set("GOOGLE_GENERATIVE_AI_API_KEY", "test-google") + set("ANTHROPIC_API_KEY", "test-anthropic") + set("OPENAI_API_KEY", "test-openai") + set("GOOGLE_GENERATIVE_AI_API_KEY", "test-google") }, fn: async () => { const providers = await list() @@ -1490,7 +1494,7 @@ test("provider env fallback - second env var used if first missing", async () => directory: tmp.path, init: async () => { // Only set fallback, not primary - Env.set("FALLBACK_KEY", "fallback-api-key") + set("FALLBACK_KEY", "fallback-api-key") }, fn: async () => { const providers = await list() @@ -1514,7 +1518,7 @@ test("getModel returns consistent results", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model1 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514")) @@ -1575,7 +1579,7 @@ test("ModelNotFoundError includes suggestions for typos", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { try { @@ -1603,7 +1607,7 @@ test("ModelNotFoundError for provider includes suggestions", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { try { @@ -1651,7 +1655,7 @@ test("getProvider returns provider info", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const provider = await getProvider(ProviderID.anthropic) @@ -1675,7 +1679,7 @@ test("closest returns undefined when no partial match found", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const result = await closest(ProviderID.anthropic, ["nonexistent-xyz-model"]) @@ -1698,7 +1702,7 @@ test("closest checks multiple query terms in order", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { // First term won't match, second will @@ -1770,7 +1774,7 @@ test("provider options are deeply merged", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1808,7 +1812,7 @@ test("custom model inherits npm package from models.dev provider config", async await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("OPENAI_API_KEY", "test-api-key") + set("OPENAI_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1843,7 +1847,7 @@ test("custom model inherits api.url from models.dev provider", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("OPENROUTER_API_KEY", "test-api-key") + set("OPENROUTER_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1944,7 +1948,7 @@ test("model variants are generated for reasoning models", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1982,7 +1986,7 @@ test("model variants can be disabled via config", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2025,7 +2029,7 @@ test("model variants can be customized via config", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2064,7 +2068,7 @@ test("disabled key is stripped from variant config", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2102,7 +2106,7 @@ test("all variants can be disabled via config", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2140,7 +2144,7 @@ test("variant config merges with generated variants", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2178,7 +2182,7 @@ test("variants filtered in second pass for database models", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("OPENAI_API_KEY", "test-api-key") + set("OPENAI_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2282,7 +2286,7 @@ test("Google Vertex: retains baseURL for custom proxy", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") + set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") }, fn: async () => { const providers = await list() @@ -2327,7 +2331,7 @@ test("Google Vertex: supports OpenAI compatible models", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") + set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") }, fn: async () => { const providers = await list() @@ -2353,9 +2357,9 @@ test("cloudflare-ai-gateway loads with env variables", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("CLOUDFLARE_ACCOUNT_ID", "test-account") - Env.set("CLOUDFLARE_GATEWAY_ID", "test-gateway") - Env.set("CLOUDFLARE_API_TOKEN", "test-token") + set("CLOUDFLARE_ACCOUNT_ID", "test-account") + set("CLOUDFLARE_GATEWAY_ID", "test-gateway") + set("CLOUDFLARE_API_TOKEN", "test-token") }, fn: async () => { const providers = await list() @@ -2385,9 +2389,9 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("CLOUDFLARE_ACCOUNT_ID", "test-account") - Env.set("CLOUDFLARE_GATEWAY_ID", "test-gateway") - Env.set("CLOUDFLARE_API_TOKEN", "test-token") + set("CLOUDFLARE_ACCOUNT_ID", "test-account") + set("CLOUDFLARE_GATEWAY_ID", "test-gateway") + set("CLOUDFLARE_API_TOKEN", "test-token") }, fn: async () => { const providers = await list() @@ -2485,8 +2489,8 @@ test("plugin config enabled and disabled providers are honored", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-anthropic-key") - Env.set("OPENAI_API_KEY", "test-openai-key") + set("ANTHROPIC_API_KEY", "test-anthropic-key") + set("OPENAI_API_KEY", "test-openai-key") }, fn: async () => { const providers = await list() diff --git a/packages/opencode/test/session/prompt-effect.test.ts b/packages/opencode/test/session/prompt-effect.test.ts index 244f778ca8..94561206e2 100644 --- a/packages/opencode/test/session/prompt-effect.test.ts +++ b/packages/opencode/test/session/prompt-effect.test.ts @@ -22,7 +22,7 @@ import { Todo } from "../../src/session/todo" import { Session } from "../../src/session" import { LLM } from "../../src/session/llm" import { MessageV2 } from "../../src/session/message-v2" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { SessionCompaction } from "../../src/session/compaction" import { SessionSummary } from "../../src/session/summary" import { Instruction } from "../../src/session/instruction" diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index c1d6f1da97..1290570b81 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1,6 +1,6 @@ import path from "path" import { describe, expect, test } from "bun:test" -import { NamedError } from "@opencode-ai/util/error" +import { NamedError } from "@opencode-ai/shared/util/error" import { fileURLToPath } from "url" import { Effect, Layer } from "effect" import { Instance } from "../../src/project/instance" diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index a598c37fcd..314306ba62 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { NamedError } from "@opencode-ai/util/error" +import type { NamedError } from "@opencode-ai/shared/util/error" import { APICallError } from "ai" import { setTimeout as sleep } from "node:timers/promises" import { Effect, Schedule } from "effect" diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 464182395a..80d74c7565 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -53,7 +53,7 @@ import { Shell } from "../../src/shell/shell" import { Snapshot } from "../../src/snapshot" import { ToolRegistry } from "../../src/tool/registry" import { Truncate } from "../../src/tool/truncate" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { Format } from "../../src/format" diff --git a/packages/opencode/test/storage/storage.test.ts b/packages/opencode/test/storage/storage.test.ts index ea8f1feb4f..60b458bb30 100644 --- a/packages/opencode/test/storage/storage.test.ts +++ b/packages/opencode/test/storage/storage.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import path from "path" import { Effect, Exit, Layer } from "effect" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import { Git } from "../../src/git" import { Global } from "../../src/global" diff --git a/packages/opencode/test/tool/apply_patch.test.ts b/packages/opencode/test/tool/apply_patch.test.ts index 03220ea3b1..c0448c78cb 100644 --- a/packages/opencode/test/tool/apply_patch.test.ts +++ b/packages/opencode/test/tool/apply_patch.test.ts @@ -5,7 +5,7 @@ import { Effect, ManagedRuntime, Layer } from "effect" import { ApplyPatchTool } from "../../src/tool/apply_patch" import { Instance } from "../../src/project/instance" import { LSP } from "../../src/lsp" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Format } from "../../src/format" import { Agent } from "../../src/agent/agent" import { Bus } from "../../src/bus" diff --git a/packages/opencode/test/tool/bash.test.ts b/packages/opencode/test/tool/bash.test.ts index fc7a9e73e4..3b03da57ee 100644 --- a/packages/opencode/test/tool/bash.test.ts +++ b/packages/opencode/test/tool/bash.test.ts @@ -12,7 +12,7 @@ import { Agent } from "../../src/agent/agent" import { Truncate } from "../../src/tool/truncate" import { SessionID, MessageID } from "../../src/session/schema" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Plugin } from "../../src/plugin" const runtime = ManagedRuntime.make( diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts index 9fe24b49b2..37a19a5fda 100644 --- a/packages/opencode/test/tool/edit.test.ts +++ b/packages/opencode/test/tool/edit.test.ts @@ -7,7 +7,7 @@ import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" import { FileTime } from "../../src/file/time" import { LSP } from "../../src/lsp" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Format } from "../../src/format" import { Agent } from "../../src/agent/agent" import { Bus } from "../../src/bus" diff --git a/packages/opencode/test/tool/glob.test.ts b/packages/opencode/test/tool/glob.test.ts index 092885ed18..20e761fc10 100644 --- a/packages/opencode/test/tool/glob.test.ts +++ b/packages/opencode/test/tool/glob.test.ts @@ -5,7 +5,7 @@ import { GlobTool } from "../../src/tool/glob" import { SessionID, MessageID } from "../../src/session/schema" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { Truncate } from "../../src/tool/truncate" import { Agent } from "../../src/agent/agent" import { provideTmpdirInstance } from "../fixture/fixture" diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index 7cdf6a0aa1..35467aeab4 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -8,7 +8,7 @@ import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import { Truncate } from "../../src/tool/truncate" import { Agent } from "../../src/agent/agent" import { Ripgrep } from "../../src/file/ripgrep" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { testEffect } from "../lib/effect" const it = testEffect( diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index 2064193d5b..f14ec33105 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -3,7 +3,7 @@ import { Cause, Effect, Exit, Layer } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { FileTime } from "../../src/file/time" import { LSP } from "../../src/lsp" import { Permission } from "../../src/permission" diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts index f7daa1e971..e83ec2efdb 100644 --- a/packages/opencode/test/tool/write.test.ts +++ b/packages/opencode/test/tool/write.test.ts @@ -5,7 +5,7 @@ import fs from "fs/promises" import { WriteTool } from "../../src/tool/write" import { Instance } from "../../src/project/instance" import { LSP } from "../../src/lsp" -import { AppFileSystem } from "../../src/filesystem" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { FileTime } from "../../src/file/time" import { Bus } from "../../src/bus" import { Format } from "../../src/format" diff --git a/packages/opencode/test/util/glob.test.ts b/packages/opencode/test/util/glob.test.ts index e58d92c85c..e982d5194c 100644 --- a/packages/opencode/test/util/glob.test.ts +++ b/packages/opencode/test/util/glob.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect } from "bun:test" import path from "path" import fs from "fs/promises" -import { Glob } from "../../src/util/glob" +import { Glob } from "@opencode-ai/shared/util/glob" import { tmpdir } from "../fixture/fixture" describe("Glob", () => { diff --git a/packages/opencode/test/util/module.test.ts b/packages/opencode/test/util/module.test.ts index 738b4a785b..6f8539bfb7 100644 --- a/packages/opencode/test/util/module.test.ts +++ b/packages/opencode/test/util/module.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" import path from "path" -import { Module } from "@opencode-ai/util/module" +import { Module } from "@opencode-ai/shared/util/module" import { Filesystem } from "../../src/util/filesystem" import { tmpdir } from "../fixture/fixture" diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 9291d0f7fe..76fe2e862f 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.4.4", + "version": "1.4.6", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 84c085bea2..49c441600c 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.4.4", + "version": "1.4.6", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index b5fc976bba..d7bf43f506 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -35,6 +35,8 @@ import type { ExperimentalWorkspaceListResponses, ExperimentalWorkspaceRemoveErrors, ExperimentalWorkspaceRemoveResponses, + ExperimentalWorkspaceSessionRestoreErrors, + ExperimentalWorkspaceSessionRestoreResponses, ExperimentalWorkspaceStatusResponses, FileListResponses, FilePartInput, @@ -157,6 +159,10 @@ import type { SessionUpdateErrors, SessionUpdateResponses, SubtaskPartInput, + SyncHistoryListErrors, + SyncHistoryListResponses, + SyncReplayErrors, + SyncReplayResponses, TextPartInput, ToolIdsErrors, ToolIdsResponses, @@ -1243,6 +1249,49 @@ export class Workspace extends HeyApiClient { }) } + /** + * Restore session into workspace + * + * Replay a session's sync events into the target workspace in batches. + */ + public sessionRestore( + parameters: { + id: string + directory?: string + workspace?: string + sessionID?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "id" }, + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { in: "body", key: "sessionID" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post< + ExperimentalWorkspaceSessionRestoreResponses, + ExperimentalWorkspaceSessionRestoreErrors, + ThrowOnError + >({ + url: "/experimental/workspace/{id}/session-restore", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + private _adaptor?: Adaptor get adaptor(): Adaptor { return (this._adaptor ??= new Adaptor({ client: this.client })) @@ -2961,6 +3010,109 @@ export class Provider extends HeyApiClient { } } +export class History extends HeyApiClient { + /** + * List sync events + * + * List sync events for all aggregates. Keys are aggregate IDs the client already knows about, values are the last known sequence ID. Events with seq > value are returned for those aggregates. Aggregates not listed in the input get their full history. + */ + public list( + parameters?: { + directory?: string + workspace?: string + body?: { + [key: string]: number + } + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + { key: "body", map: "body" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/sync/history", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } +} + +export class Sync extends HeyApiClient { + /** + * Replay sync events + * + * Validate and replay a complete sync event history. + */ + public replay( + parameters?: { + query_directory?: string + workspace?: string + body_directory?: string + events?: Array<{ + id: string + aggregateID: string + seq: number + type: string + data: { + [key: string]: unknown + } + }> + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { + in: "query", + key: "query_directory", + map: "directory", + }, + { in: "query", key: "workspace" }, + { + in: "body", + key: "body_directory", + map: "directory", + }, + { in: "body", key: "events" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post({ + url: "/sync/replay", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } + + private _history?: History + get history(): History { + return (this._history ??= new History({ client: this.client })) + } +} + export class Find extends HeyApiClient { /** * Find text @@ -4217,6 +4369,11 @@ export class OpencodeClient extends HeyApiClient { return (this._provider ??= new Provider({ client: this.client })) } + private _sync?: Sync + get sync(): Sync { + return (this._sync ??= new Sync({ client: this.client })) + } + private _find?: Find get find(): Find { return (this._find ??= new Find({ client: this.client })) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 8f4c16c5bd..24c1d53bf7 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -520,6 +520,16 @@ export type EventWorkspaceFailed = { } } +export type EventWorkspaceRestore = { + type: "workspace.restore" + properties: { + workspaceID: string + sessionID: string + total: number + step: number + } +} + export type EventWorkspaceStatus = { type: "workspace.status" properties: { @@ -1137,6 +1147,7 @@ export type GlobalEvent = { | EventPtyDeleted | EventWorkspaceReady | EventWorkspaceFailed + | EventWorkspaceRestore | EventWorkspaceStatus | EventMessageUpdated | EventMessageRemoved @@ -2049,6 +2060,7 @@ export type Event = | EventPtyDeleted | EventWorkspaceReady | EventWorkspaceFailed + | EventWorkspaceRestore | EventWorkspaceStatus | EventMessageUpdated | EventMessageRemoved @@ -3006,6 +3018,42 @@ export type ExperimentalWorkspaceRemoveResponses = { export type ExperimentalWorkspaceRemoveResponse = ExperimentalWorkspaceRemoveResponses[keyof ExperimentalWorkspaceRemoveResponses] +export type ExperimentalWorkspaceSessionRestoreData = { + body?: { + sessionID: string + } + path: { + id: string + } + query?: { + directory?: string + workspace?: string + } + url: "/experimental/workspace/{id}/session-restore" +} + +export type ExperimentalWorkspaceSessionRestoreErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type ExperimentalWorkspaceSessionRestoreError = + ExperimentalWorkspaceSessionRestoreErrors[keyof ExperimentalWorkspaceSessionRestoreErrors] + +export type ExperimentalWorkspaceSessionRestoreResponses = { + /** + * Session replay started + */ + 200: { + total: number + } +} + +export type ExperimentalWorkspaceSessionRestoreResponse = + ExperimentalWorkspaceSessionRestoreResponses[keyof ExperimentalWorkspaceSessionRestoreResponses] + export type WorktreeRemoveData = { body?: WorktreeRemoveInput path?: never @@ -4456,6 +4504,85 @@ export type ProviderOauthCallbackResponses = { export type ProviderOauthCallbackResponse = ProviderOauthCallbackResponses[keyof ProviderOauthCallbackResponses] +export type SyncReplayData = { + body?: { + directory: string + events: Array<{ + id: string + aggregateID: string + seq: number + type: string + data: { + [key: string]: unknown + } + }> + } + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/sync/replay" +} + +export type SyncReplayErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type SyncReplayError = SyncReplayErrors[keyof SyncReplayErrors] + +export type SyncReplayResponses = { + /** + * Replayed sync events + */ + 200: { + sessionID: string + } +} + +export type SyncReplayResponse = SyncReplayResponses[keyof SyncReplayResponses] + +export type SyncHistoryListData = { + body?: { + [key: string]: number + } + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/sync/history" +} + +export type SyncHistoryListErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type SyncHistoryListError = SyncHistoryListErrors[keyof SyncHistoryListErrors] + +export type SyncHistoryListResponses = { + /** + * Sync events + */ + 200: Array<{ + id: string + aggregate_id: string + seq: number + type: string + data: { + [key: string]: unknown + } + }> +} + +export type SyncHistoryListResponse = SyncHistoryListResponses[keyof SyncHistoryListResponses] + export type FindTextData = { body?: never path?: never diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 6000e66042..ee3538d55f 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -1805,6 +1805,90 @@ ] } }, + "/experimental/workspace/{id}/session-restore": { + "post": { + "operationId": "experimental.workspace.sessionRestore", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "id", + "schema": { + "type": "string", + "pattern": "^wrk.*" + }, + "required": true + } + ], + "summary": "Restore session into workspace", + "description": "Replay a session's sync events into the target workspace in batches.", + "responses": { + "200": { + "description": "Session replay started", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + }, + "required": ["total"] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "pattern": "^ses.*" + } + }, + "required": ["sessionID"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.experimental.workspace.sessionRestore({\n ...\n})" + } + ] + } + }, "/experimental/worktree": { "post": { "operationId": "worktree.create", @@ -5143,6 +5227,202 @@ ] } }, + "/sync/replay": { + "post": { + "operationId": "sync.replay", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + } + ], + "summary": "Replay sync events", + "description": "Validate and replay a complete sync event history.", + "responses": { + "200": { + "description": "Replayed sync events", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionID": { + "type": "string" + } + }, + "required": ["sessionID"] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "events": { + "minItems": 1, + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "aggregateID": { + "type": "string" + }, + "seq": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "type": { + "type": "string" + }, + "data": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["id", "aggregateID", "seq", "type", "data"] + } + } + }, + "required": ["directory", "events"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.sync.replay({\n ...\n})" + } + ] + } + }, + "/sync/history": { + "get": { + "operationId": "sync.history.list", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "workspace", + "schema": { + "type": "string" + } + } + ], + "summary": "List sync events", + "description": "List sync events for all aggregates. Keys are aggregate IDs the client already knows about, values are the last known sequence ID. Events with seq > value are returned for those aggregates. Aggregates not listed in the input get their full history.", + "responses": { + "200": { + "description": "Sync events", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "aggregate_id": { + "type": "string" + }, + "seq": { + "type": "number" + }, + "type": { + "type": "string" + }, + "data": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["id", "aggregate_id", "seq", "type", "data"] + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.sync.history.list({\n ...\n})" + } + ] + } + }, "/find": { "get": { "operationId": "find.text", @@ -8514,6 +8794,40 @@ }, "required": ["type", "properties"] }, + "Event.workspace.restore": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "workspace.restore" + }, + "properties": { + "type": "object", + "properties": { + "workspaceID": { + "type": "string", + "pattern": "^wrk.*" + }, + "sessionID": { + "type": "string", + "pattern": "^ses.*" + }, + "total": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "step": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + }, + "required": ["workspaceID", "sessionID", "total", "step"] + } + }, + "required": ["type", "properties"] + }, "Event.workspace.status": { "type": "object", "properties": { @@ -10523,6 +10837,9 @@ { "$ref": "#/components/schemas/Event.workspace.failed" }, + { + "$ref": "#/components/schemas/Event.workspace.restore" + }, { "$ref": "#/components/schemas/Event.workspace.status" }, @@ -12780,6 +13097,9 @@ { "$ref": "#/components/schemas/Event.workspace.failed" }, + { + "$ref": "#/components/schemas/Event.workspace.restore" + }, { "$ref": "#/components/schemas/Event.workspace.status" }, diff --git a/packages/server/package.json b/packages/server/package.json index ec4c325fd6..9b8b31299d 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/server", - "version": "1.4.4", + "version": "1.4.6", "type": "module", "license": "MIT", "exports": { @@ -17,10 +17,11 @@ "dist" ], "scripts": { - "typecheck": "tsc --noEmit", + "typecheck": "tsgo --noEmit", "build": "tsc" }, "devDependencies": { + "@typescript/native-preview": "catalog:", "typescript": "catalog:" }, "dependencies": { diff --git a/packages/util/sst-env.d.ts b/packages/server/sst-env.d.ts similarity index 100% rename from packages/util/sst-env.d.ts rename to packages/server/sst-env.d.ts diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000000..252b381d48 --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "version": "1.4.6", + "name": "@opencode-ai/shared", + "type": "module", + "license": "MIT", + "private": true, + "scripts": { + "test": "bun test" + }, + "bin": { + "opencode": "./bin/opencode" + }, + "exports": { + "./*": "./src/*.ts" + }, + "imports": {}, + "devDependencies": { + "@types/semver": "catalog:", + "@types/bun": "catalog:" + }, + "dependencies": { + "@effect/platform-node": "catalog:", + "@npmcli/arborist": "catalog:", + "effect": "catalog:", + "mime-types": "3.0.2", + "minimatch": "10.2.5", + "semver": "catalog:", + "xdg-basedir": "5.1.0", + "zod": "catalog:" + }, + "overrides": { + "drizzle-orm": "catalog:" + } +} diff --git a/packages/opencode/src/filesystem/index.ts b/packages/shared/src/filesystem.ts similarity index 99% rename from packages/opencode/src/filesystem/index.ts rename to packages/shared/src/filesystem.ts index 2c3964ec25..44346be8f9 100644 --- a/packages/opencode/src/filesystem/index.ts +++ b/packages/shared/src/filesystem.ts @@ -5,7 +5,7 @@ import * as NFS from "fs/promises" import { lookup } from "mime-types" import { Effect, FileSystem, Layer, Schema, Context } from "effect" import type { PlatformError } from "effect/PlatformError" -import { Glob } from "../util/glob" +import { Glob } from "./util/glob" export namespace AppFileSystem { export class FileSystemError extends Schema.TaggedErrorClass()("FileSystemError", { diff --git a/packages/shared/src/global.ts b/packages/shared/src/global.ts new file mode 100644 index 0000000000..538cc091b5 --- /dev/null +++ b/packages/shared/src/global.ts @@ -0,0 +1,42 @@ +import path from "path" +import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir" +import os from "os" +import { Context, Effect, Layer } from "effect" + +export namespace Global { + export class Service extends Context.Service()("@opencode/Global") {} + + export interface Interface { + readonly home: string + readonly data: string + readonly cache: string + readonly config: string + readonly state: string + readonly bin: string + readonly log: string + } + + export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const app = "opencode" + const home = process.env.OPENCODE_TEST_HOME ?? os.homedir() + const data = path.join(xdgData!, app) + const cache = path.join(xdgCache!, app) + const cfg = path.join(xdgConfig!, app) + const state = path.join(xdgState!, app) + const bin = path.join(cache, "bin") + const log = path.join(data, "log") + + return Service.of({ + home, + data, + cache, + config: cfg, + state, + bin, + log, + }) + }), + ) +} diff --git a/packages/shared/src/npm.ts b/packages/shared/src/npm.ts new file mode 100644 index 0000000000..994ec04dae --- /dev/null +++ b/packages/shared/src/npm.ts @@ -0,0 +1,247 @@ +import path from "path" +import semver from "semver" +import { Arborist } from "@npmcli/arborist" +import { Effect, Schema, Context, Layer, Option, FileSystem } from "effect" +import { NodeFileSystem } from "@effect/platform-node" +import { AppFileSystem } from "./filesystem" +import { Global } from "./global" +import { Flock } from "./util/flock" + +export namespace Npm { + export class InstallFailedError extends Schema.TaggedErrorClass()("NpmInstallFailedError", { + pkg: Schema.String, + cause: Schema.optional(Schema.Defect), + }) {} + + export interface EntryPoint { + readonly directory: string + readonly entrypoint: Option.Option + } + + export interface Interface { + readonly add: (pkg: string) => Effect.Effect + readonly install: (dir: string) => Effect.Effect + readonly outdated: (pkg: string, cachedVersion: string) => Effect.Effect + readonly which: (pkg: string) => Effect.Effect> + } + + export class Service extends Context.Service()("@opencode/Npm") {} + + const illegal = process.platform === "win32" ? new Set(["<", ">", ":", '"', "|", "?", "*"]) : undefined + + export function sanitize(pkg: string) { + if (!illegal) return pkg + return Array.from(pkg, (char) => (illegal.has(char) || char.charCodeAt(0) < 32 ? "_" : char)).join("") + } + + const resolveEntryPoint = (name: string, dir: string): EntryPoint => { + let entrypoint: Option.Option + try { + const resolved = typeof Bun !== "undefined" ? import.meta.resolve(name, dir) : import.meta.resolve(dir) + entrypoint = Option.some(resolved) + } catch { + entrypoint = Option.none() + } + return { + directory: dir, + entrypoint, + } + } + + interface ArboristNode { + name: string + path: string + } + + interface ArboristTree { + edgesOut: Map + } + export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const afs = yield* AppFileSystem.Service + const global = yield* Global.Service + const fs = yield* FileSystem.FileSystem + const directory = (pkg: string) => path.join(global.cache, "packages", sanitize(pkg)) + + const outdated = Effect.fn("Npm.outdated")(function* (pkg: string, cachedVersion: string) { + const response = yield* Effect.tryPromise({ + try: () => fetch(`https://registry.npmjs.org/${pkg}`), + catch: () => undefined, + }).pipe(Effect.orElseSucceed(() => undefined)) + + if (!response || !response.ok) { + return false + } + + const data = yield* Effect.tryPromise({ + try: () => response.json() as Promise<{ "dist-tags"?: { latest?: string } }>, + catch: () => undefined, + }).pipe(Effect.orElseSucceed(() => undefined)) + + const latestVersion = data?.["dist-tags"]?.latest + if (!latestVersion) { + return false + } + + const range = /[\s^~*xX<>|=]/.test(cachedVersion) + if (range) return !semver.satisfies(latestVersion, cachedVersion) + + return semver.lt(cachedVersion, latestVersion) + }) + + const add = Effect.fn("Npm.add")(function* (pkg: string) { + const dir = directory(pkg) + yield* Flock.effect(`npm-install:${dir}`) + + const arborist = new Arborist({ + path: dir, + binLinks: true, + progress: false, + savePrefix: "", + ignoreScripts: true, + }) + + const tree = yield* Effect.tryPromise({ + try: () => arborist.loadVirtual().catch(() => undefined), + catch: () => undefined, + }).pipe(Effect.orElseSucceed(() => undefined)) as Effect.Effect + + if (tree) { + const first = tree.edgesOut.values().next().value?.to + if (first) { + return resolveEntryPoint(first.name, first.path) + } + } + + const result = yield* Effect.tryPromise({ + try: () => + arborist.reify({ + add: [pkg], + save: true, + saveType: "prod", + }), + catch: (cause) => new InstallFailedError({ pkg, cause }), + }) as Effect.Effect + + const first = result.edgesOut.values().next().value?.to + if (!first) { + return yield* new InstallFailedError({ pkg }) + } + + return resolveEntryPoint(first.name, first.path) + }, Effect.scoped) + + const install = Effect.fn("Npm.install")(function* (dir: string) { + yield* Flock.effect(`npm-install:${dir}`) + + const reify = Effect.fnUntraced(function* () { + const arb = new Arborist({ + path: dir, + binLinks: true, + progress: false, + savePrefix: "", + ignoreScripts: true, + }) + yield* Effect.tryPromise({ + try: () => arb.reify().catch(() => {}), + catch: () => {}, + }).pipe(Effect.orElseSucceed(() => {})) + }) + + const nodeModulesExists = yield* afs.existsSafe(path.join(dir, "node_modules")) + if (!nodeModulesExists) { + yield* reify() + return + } + + const pkg = yield* afs.readJson(path.join(dir, "package.json")).pipe(Effect.orElseSucceed(() => ({}))) + const lock = yield* afs.readJson(path.join(dir, "package-lock.json")).pipe(Effect.orElseSucceed(() => ({}))) + + const pkgAny = pkg as any + const lockAny = lock as any + + const declared = new Set([ + ...Object.keys(pkgAny?.dependencies || {}), + ...Object.keys(pkgAny?.devDependencies || {}), + ...Object.keys(pkgAny?.peerDependencies || {}), + ...Object.keys(pkgAny?.optionalDependencies || {}), + ]) + + const root = lockAny?.packages?.[""] || {} + const locked = new Set([ + ...Object.keys(root?.dependencies || {}), + ...Object.keys(root?.devDependencies || {}), + ...Object.keys(root?.peerDependencies || {}), + ...Object.keys(root?.optionalDependencies || {}), + ]) + + for (const name of declared) { + if (!locked.has(name)) { + yield* reify() + return + } + } + }, Effect.scoped) + + const which = Effect.fn("Npm.which")(function* (pkg: string) { + const dir = directory(pkg) + const binDir = path.join(dir, "node_modules", ".bin") + + const pick = Effect.fnUntraced(function* () { + const files = yield* fs.readDirectory(binDir).pipe(Effect.catch(() => Effect.succeed([] as string[]))) + + if (files.length === 0) return Option.none() + if (files.length === 1) return Option.some(files[0]) + + const pkgJson = yield* afs.readJson(path.join(dir, "node_modules", pkg, "package.json")).pipe(Effect.option) + + if (Option.isSome(pkgJson)) { + const parsed = pkgJson.value as { bin?: string | Record } + if (parsed?.bin) { + const unscoped = pkg.startsWith("@") ? pkg.split("/")[1] : pkg + const bin = parsed.bin + if (typeof bin === "string") return Option.some(unscoped) + const keys = Object.keys(bin) + if (keys.length === 1) return Option.some(keys[0]) + return bin[unscoped] ? Option.some(unscoped) : Option.some(keys[0]) + } + } + + return Option.some(files[0]) + }) + + return yield* Effect.gen(function* () { + const bin = yield* pick() + if (Option.isSome(bin)) { + return Option.some(path.join(binDir, bin.value)) + } + + yield* fs.remove(path.join(dir, "package-lock.json")).pipe(Effect.orElseSucceed(() => {})) + + yield* add(pkg) + + const resolved = yield* pick() + if (Option.isNone(resolved)) return Option.none() + return Option.some(path.join(binDir, resolved.value)) + }).pipe( + Effect.scoped, + Effect.orElseSucceed(() => Option.none()), + ) + }) + + return Service.of({ + add, + install, + outdated, + which, + }) + }), + ) + + export const defaultLayer = layer.pipe( + Layer.provide(AppFileSystem.layer), + Layer.provide(Global.layer), + Layer.provide(NodeFileSystem.layer), + ) +} diff --git a/packages/shared/src/types.d.ts b/packages/shared/src/types.d.ts new file mode 100644 index 0000000000..b5d667f1d9 --- /dev/null +++ b/packages/shared/src/types.d.ts @@ -0,0 +1,44 @@ +declare module "@npmcli/arborist" { + export interface ArboristOptions { + path: string + binLinks?: boolean + progress?: boolean + savePrefix?: string + ignoreScripts?: boolean + } + + export interface ArboristNode { + name: string + path: string + } + + export interface ArboristEdge { + to?: ArboristNode + } + + export interface ArboristTree { + edgesOut: Map + } + + export interface ReifyOptions { + add?: string[] + save?: boolean + saveType?: "prod" | "dev" | "optional" | "peer" + } + + export class Arborist { + constructor(options: ArboristOptions) + loadVirtual(): Promise + reify(options?: ReifyOptions): Promise + } +} + +declare var Bun: + | { + file(path: string): { + text(): Promise + json(): Promise + } + write(path: string, content: string | Uint8Array): Promise + } + | undefined diff --git a/packages/util/src/array.ts b/packages/shared/src/util/array.ts similarity index 100% rename from packages/util/src/array.ts rename to packages/shared/src/util/array.ts diff --git a/packages/util/src/binary.ts b/packages/shared/src/util/binary.ts similarity index 100% rename from packages/util/src/binary.ts rename to packages/shared/src/util/binary.ts diff --git a/packages/util/src/encode.ts b/packages/shared/src/util/encode.ts similarity index 100% rename from packages/util/src/encode.ts rename to packages/shared/src/util/encode.ts diff --git a/packages/util/src/error.ts b/packages/shared/src/util/error.ts similarity index 100% rename from packages/util/src/error.ts rename to packages/shared/src/util/error.ts diff --git a/packages/opencode/src/util/flock.ts b/packages/shared/src/util/flock.ts similarity index 93% rename from packages/opencode/src/util/flock.ts rename to packages/shared/src/util/flock.ts index 74c7905ebb..4a1df1dee7 100644 --- a/packages/opencode/src/util/flock.ts +++ b/packages/shared/src/util/flock.ts @@ -2,11 +2,25 @@ import path from "path" import os from "os" import { randomBytes, randomUUID } from "crypto" import { mkdir, readFile, rm, stat, utimes, writeFile } from "fs/promises" -import { Global } from "@/global" -import { Hash } from "@/util/hash" +import { Hash } from "./hash" +import { Effect } from "effect" + +export type FlockGlobal = { + state: string +} export namespace Flock { - const root = path.join(Global.Path.state, "locks") + let global: FlockGlobal | undefined + + export function setGlobal(g: FlockGlobal) { + global = g + } + + const root = () => { + if (!global) throw new Error("Flock global not set") + return path.join(global.state, "locks") + } + // Defaults for callers that do not provide timing options. const defaultOpts = { staleMs: 60_000, @@ -301,7 +315,7 @@ export namespace Flock { baseDelayMs: input.baseDelayMs ?? defaultOpts.baseDelayMs, maxDelayMs: input.maxDelayMs ?? defaultOpts.maxDelayMs, } - const dir = input.dir ?? root + const dir = input.dir ?? root() await mkdir(dir, { recursive: true }) const lockfile = path.join(dir, Hash.fast(key) + ".lock") @@ -330,4 +344,11 @@ export namespace Flock { input.signal?.throwIfAborted() return await fn() } + + export const effect = Effect.fn("Flock.effect")(function* (key: string) { + return yield* Effect.acquireRelease( + Effect.promise((signal) => Flock.acquire(key, { signal })), + (foo) => Effect.promise(() => foo.release()), + ).pipe(Effect.asVoid) + }) } diff --git a/packages/util/src/fn.ts b/packages/shared/src/util/fn.ts similarity index 100% rename from packages/util/src/fn.ts rename to packages/shared/src/util/fn.ts diff --git a/packages/opencode/src/util/glob.ts b/packages/shared/src/util/glob.ts similarity index 100% rename from packages/opencode/src/util/glob.ts rename to packages/shared/src/util/glob.ts diff --git a/packages/opencode/src/util/hash.ts b/packages/shared/src/util/hash.ts similarity index 100% rename from packages/opencode/src/util/hash.ts rename to packages/shared/src/util/hash.ts diff --git a/packages/util/src/identifier.ts b/packages/shared/src/util/identifier.ts similarity index 100% rename from packages/util/src/identifier.ts rename to packages/shared/src/util/identifier.ts diff --git a/packages/util/src/iife.ts b/packages/shared/src/util/iife.ts similarity index 100% rename from packages/util/src/iife.ts rename to packages/shared/src/util/iife.ts diff --git a/packages/util/src/lazy.ts b/packages/shared/src/util/lazy.ts similarity index 100% rename from packages/util/src/lazy.ts rename to packages/shared/src/util/lazy.ts diff --git a/packages/util/src/module.ts b/packages/shared/src/util/module.ts similarity index 100% rename from packages/util/src/module.ts rename to packages/shared/src/util/module.ts diff --git a/packages/util/src/path.ts b/packages/shared/src/util/path.ts similarity index 100% rename from packages/util/src/path.ts rename to packages/shared/src/util/path.ts diff --git a/packages/util/src/retry.ts b/packages/shared/src/util/retry.ts similarity index 100% rename from packages/util/src/retry.ts rename to packages/shared/src/util/retry.ts diff --git a/packages/util/src/slug.ts b/packages/shared/src/util/slug.ts similarity index 100% rename from packages/util/src/slug.ts rename to packages/shared/src/util/slug.ts diff --git a/packages/shared/test/filesystem/filesystem.test.ts b/packages/shared/test/filesystem/filesystem.test.ts new file mode 100644 index 0000000000..ce990d3795 --- /dev/null +++ b/packages/shared/test/filesystem/filesystem.test.ts @@ -0,0 +1,338 @@ +import { describe, test, expect } from "bun:test" +import { Effect, Layer, FileSystem } from "effect" +import { NodeFileSystem } from "@effect/platform-node" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { testEffect } from "../lib/effect" +import path from "path" + +const live = AppFileSystem.layer.pipe(Layer.provideMerge(NodeFileSystem.layer)) +const { effect: it } = testEffect(live) + +describe("AppFileSystem", () => { + describe("isDir", () => { + it( + "returns true for directories", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + expect(yield* fs.isDir(tmp)).toBe(true) + }), + ) + + it( + "returns false for files", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + const file = path.join(tmp, "test.txt") + yield* filesys.writeFileString(file, "hello") + expect(yield* fs.isDir(file)).toBe(false) + }), + ) + + it( + "returns false for non-existent paths", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + expect(yield* fs.isDir("/tmp/nonexistent-" + Math.random())).toBe(false) + }), + ) + }) + + describe("isFile", () => { + it( + "returns true for files", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + const file = path.join(tmp, "test.txt") + yield* filesys.writeFileString(file, "hello") + expect(yield* fs.isFile(file)).toBe(true) + }), + ) + + it( + "returns false for directories", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + expect(yield* fs.isFile(tmp)).toBe(false) + }), + ) + }) + + describe("readJson / writeJson", () => { + it( + "round-trips JSON data", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + const file = path.join(tmp, "data.json") + const data = { name: "test", count: 42, nested: { ok: true } } + + yield* fs.writeJson(file, data) + const result = yield* fs.readJson(file) + + expect(result).toEqual(data) + }), + ) + }) + + describe("ensureDir", () => { + it( + "creates nested directories", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + const nested = path.join(tmp, "a", "b", "c") + + yield* fs.ensureDir(nested) + + const info = yield* filesys.stat(nested) + expect(info.type).toBe("Directory") + }), + ) + + it( + "is idempotent", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + const dir = path.join(tmp, "existing") + yield* filesys.makeDirectory(dir) + + yield* fs.ensureDir(dir) + + const info = yield* filesys.stat(dir) + expect(info.type).toBe("Directory") + }), + ) + }) + + describe("writeWithDirs", () => { + it( + "creates parent directories if missing", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + const file = path.join(tmp, "deep", "nested", "file.txt") + + yield* fs.writeWithDirs(file, "hello") + + expect(yield* filesys.readFileString(file)).toBe("hello") + }), + ) + + it( + "writes directly when parent exists", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + const file = path.join(tmp, "direct.txt") + + yield* fs.writeWithDirs(file, "world") + + expect(yield* filesys.readFileString(file)).toBe("world") + }), + ) + + it( + "writes Uint8Array content", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + const file = path.join(tmp, "binary.bin") + const content = new Uint8Array([0x00, 0x01, 0x02, 0x03]) + + yield* fs.writeWithDirs(file, content) + + const result = yield* filesys.readFile(file) + expect(new Uint8Array(result)).toEqual(content) + }), + ) + }) + + describe("findUp", () => { + it( + "finds target in start directory", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + yield* filesys.writeFileString(path.join(tmp, "target.txt"), "found") + + const result = yield* fs.findUp("target.txt", tmp) + expect(result).toEqual([path.join(tmp, "target.txt")]) + }), + ) + + it( + "finds target in parent directories", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + yield* filesys.writeFileString(path.join(tmp, "marker"), "root") + const child = path.join(tmp, "a", "b") + yield* filesys.makeDirectory(child, { recursive: true }) + + const result = yield* fs.findUp("marker", child, tmp) + expect(result).toEqual([path.join(tmp, "marker")]) + }), + ) + + it( + "returns empty array when not found", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + const result = yield* fs.findUp("nonexistent", tmp, tmp) + expect(result).toEqual([]) + }), + ) + }) + + describe("up", () => { + it( + "finds multiple targets walking up", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + yield* filesys.writeFileString(path.join(tmp, "a.txt"), "a") + yield* filesys.writeFileString(path.join(tmp, "b.txt"), "b") + const child = path.join(tmp, "sub") + yield* filesys.makeDirectory(child) + yield* filesys.writeFileString(path.join(child, "a.txt"), "a-child") + + const result = yield* fs.up({ targets: ["a.txt", "b.txt"], start: child, stop: tmp }) + + expect(result).toContain(path.join(child, "a.txt")) + expect(result).toContain(path.join(tmp, "a.txt")) + expect(result).toContain(path.join(tmp, "b.txt")) + }), + ) + }) + + describe("glob", () => { + it( + "finds files matching pattern", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + yield* filesys.writeFileString(path.join(tmp, "a.ts"), "a") + yield* filesys.writeFileString(path.join(tmp, "b.ts"), "b") + yield* filesys.writeFileString(path.join(tmp, "c.json"), "c") + + const result = yield* fs.glob("*.ts", { cwd: tmp }) + expect(result.sort()).toEqual(["a.ts", "b.ts"]) + }), + ) + + it( + "supports absolute paths", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + yield* filesys.writeFileString(path.join(tmp, "file.txt"), "hello") + + const result = yield* fs.glob("*.txt", { cwd: tmp, absolute: true }) + expect(result).toEqual([path.join(tmp, "file.txt")]) + }), + ) + }) + + describe("globMatch", () => { + it( + "matches patterns", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + expect(fs.globMatch("*.ts", "foo.ts")).toBe(true) + expect(fs.globMatch("*.ts", "foo.json")).toBe(false) + expect(fs.globMatch("src/**", "src/a/b.ts")).toBe(true) + }), + ) + }) + + describe("globUp", () => { + it( + "finds files walking up directories", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + yield* filesys.writeFileString(path.join(tmp, "root.md"), "root") + const child = path.join(tmp, "a", "b") + yield* filesys.makeDirectory(child, { recursive: true }) + yield* filesys.writeFileString(path.join(child, "leaf.md"), "leaf") + + const result = yield* fs.globUp("*.md", child, tmp) + expect(result).toContain(path.join(child, "leaf.md")) + expect(result).toContain(path.join(tmp, "root.md")) + }), + ) + }) + + describe("built-in passthrough", () => { + it( + "exists works", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + const file = path.join(tmp, "exists.txt") + yield* filesys.writeFileString(file, "yes") + + expect(yield* filesys.exists(file)).toBe(true) + expect(yield* filesys.exists(file + ".nope")).toBe(false) + }), + ) + + it( + "remove works", + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const filesys = yield* FileSystem.FileSystem + const tmp = yield* filesys.makeTempDirectoryScoped() + const file = path.join(tmp, "delete-me.txt") + yield* filesys.writeFileString(file, "bye") + + yield* filesys.remove(file) + + expect(yield* filesys.exists(file)).toBe(false) + }), + ) + }) + + describe("pure helpers", () => { + test("mimeType returns correct types", () => { + expect(AppFileSystem.mimeType("file.json")).toBe("application/json") + expect(AppFileSystem.mimeType("image.png")).toBe("image/png") + expect(AppFileSystem.mimeType("unknown.qzx")).toBe("application/octet-stream") + }) + + test("contains checks path containment", () => { + expect(AppFileSystem.contains("/a/b", "/a/b/c")).toBe(true) + expect(AppFileSystem.contains("/a/b", "/a/c")).toBe(false) + }) + + test("overlaps detects overlapping paths", () => { + expect(AppFileSystem.overlaps("/a/b", "/a/b/c")).toBe(true) + expect(AppFileSystem.overlaps("/a/b/c", "/a/b")).toBe(true) + expect(AppFileSystem.overlaps("/a", "/b")).toBe(false) + }) + }) +}) diff --git a/packages/shared/test/fixture/flock-worker.ts b/packages/shared/test/fixture/flock-worker.ts new file mode 100644 index 0000000000..9954d290cc --- /dev/null +++ b/packages/shared/test/fixture/flock-worker.ts @@ -0,0 +1,72 @@ +import fs from "fs/promises" +import { Flock } from "@opencode-ai/shared/util/flock" + +type Msg = { + key: string + dir: string + staleMs?: number + timeoutMs?: number + baseDelayMs?: number + maxDelayMs?: number + holdMs?: number + ready?: string + active?: string + done?: string +} + +function sleep(ms: number) { + return new Promise((resolve) => { + setTimeout(resolve, ms) + }) +} + +function input() { + const raw = process.argv[2] + if (!raw) { + throw new Error("Missing flock worker input") + } + + return JSON.parse(raw) as Msg +} + +async function job(input: Msg) { + if (input.ready) { + await fs.writeFile(input.ready, String(process.pid)) + } + + if (input.active) { + await fs.writeFile(input.active, String(process.pid), { flag: "wx" }) + } + + try { + if (input.holdMs && input.holdMs > 0) { + await sleep(input.holdMs) + } + + if (input.done) { + await fs.appendFile(input.done, "1\n") + } + } finally { + if (input.active) { + await fs.rm(input.active, { force: true }) + } + } +} + +async function main() { + const msg = input() + + await Flock.withLock(msg.key, () => job(msg), { + dir: msg.dir, + staleMs: msg.staleMs, + timeoutMs: msg.timeoutMs, + baseDelayMs: msg.baseDelayMs, + maxDelayMs: msg.maxDelayMs, + }) +} + +await main().catch((err) => { + const text = err instanceof Error ? (err.stack ?? err.message) : String(err) + process.stderr.write(text) + process.exit(1) +}) diff --git a/packages/shared/test/lib/effect.ts b/packages/shared/test/lib/effect.ts new file mode 100644 index 0000000000..131ec5cc6b --- /dev/null +++ b/packages/shared/test/lib/effect.ts @@ -0,0 +1,53 @@ +import { test, type TestOptions } from "bun:test" +import { Cause, Effect, Exit, Layer } from "effect" +import type * as Scope from "effect/Scope" +import * as TestClock from "effect/testing/TestClock" +import * as TestConsole from "effect/testing/TestConsole" + +type Body = Effect.Effect | (() => Effect.Effect) + +const body = (value: Body) => Effect.suspend(() => (typeof value === "function" ? value() : value)) + +const run = (value: Body, layer: Layer.Layer) => + Effect.gen(function* () { + const exit = yield* body(value).pipe(Effect.scoped, Effect.provide(layer), Effect.exit) + if (Exit.isFailure(exit)) { + for (const err of Cause.prettyErrors(exit.cause)) { + yield* Effect.logError(err) + } + } + return yield* exit + }).pipe(Effect.runPromise) + +const make = (testLayer: Layer.Layer, liveLayer: Layer.Layer) => { + const effect = (name: string, value: Body, opts?: number | TestOptions) => + test(name, () => run(value, testLayer), opts) + + effect.only = (name: string, value: Body, opts?: number | TestOptions) => + test.only(name, () => run(value, testLayer), opts) + + effect.skip = (name: string, value: Body, opts?: number | TestOptions) => + test.skip(name, () => run(value, testLayer), opts) + + const live = (name: string, value: Body, opts?: number | TestOptions) => + test(name, () => run(value, liveLayer), opts) + + live.only = (name: string, value: Body, opts?: number | TestOptions) => + test.only(name, () => run(value, liveLayer), opts) + + live.skip = (name: string, value: Body, opts?: number | TestOptions) => + test.skip(name, () => run(value, liveLayer), opts) + + return { effect, live } +} + +// Test environment with TestClock and TestConsole +const testEnv = Layer.mergeAll(TestConsole.layer, TestClock.layer()) + +// Live environment - uses real clock, but keeps TestConsole for output capture +const liveEnv = TestConsole.layer + +export const it = make(testEnv, liveEnv) + +export const testEffect = (layer: Layer.Layer) => + make(Layer.provideMerge(layer, testEnv), Layer.provideMerge(layer, liveEnv)) diff --git a/packages/shared/test/npm.test.ts b/packages/shared/test/npm.test.ts new file mode 100644 index 0000000000..4443d2985c --- /dev/null +++ b/packages/shared/test/npm.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, test } from "bun:test" +import { Npm } from "@opencode-ai/shared/npm" + +const win = process.platform === "win32" + +describe("Npm.sanitize", () => { + test("keeps normal scoped package specs unchanged", () => { + expect(Npm.sanitize("@opencode/acme")).toBe("@opencode/acme") + expect(Npm.sanitize("@opencode/acme@1.0.0")).toBe("@opencode/acme@1.0.0") + expect(Npm.sanitize("prettier")).toBe("prettier") + }) + + test("handles git https specs", () => { + const spec = "acme@git+https://github.com/opencode/acme.git" + const expected = win ? "acme@git+https_//github.com/opencode/acme.git" : spec + expect(Npm.sanitize(spec)).toBe(expected) + }) +}) diff --git a/packages/opencode/test/util/flock.test.ts b/packages/shared/test/util/flock.test.ts similarity index 80% rename from packages/opencode/test/util/flock.test.ts rename to packages/shared/test/util/flock.test.ts index fedbfb0697..f1053dfd2b 100644 --- a/packages/opencode/test/util/flock.test.ts +++ b/packages/shared/test/util/flock.test.ts @@ -1,14 +1,10 @@ import { describe, expect, test } from "bun:test" import fs from "fs/promises" +import { spawn } from "child_process" import path from "path" -import { Flock } from "../../src/util/flock" -import { Hash } from "../../src/util/hash" -import { Process } from "../../src/util/process" -import { Filesystem } from "../../src/util/filesystem" -import { tmpdir } from "../fixture/fixture" - -const root = path.join(import.meta.dir, "../..") -const worker = path.join(import.meta.dir, "../fixture/flock-worker.ts") +import os from "os" +import { Flock } from "@opencode-ai/shared/util/flock" +import { Hash } from "@opencode-ai/shared/util/hash" type Msg = { key: string @@ -23,6 +19,19 @@ type Msg = { done?: string } +const root = path.join(import.meta.dir, "../..") +const worker = path.join(import.meta.dir, "../fixture/flock-worker.ts") + +async function tmpdir() { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "flock-test-")) + return { + path: dir, + async [Symbol.asyncDispose]() { + await fs.rm(dir, { recursive: true, force: true }) + }, + } +} + function lock(dir: string, key: string) { return path.join(dir, Hash.fast(key) + ".lock") } @@ -51,21 +60,55 @@ async function wait(file: string, timeout = 3_000) { } function run(msg: Msg) { - return Process.run([process.execPath, worker, JSON.stringify(msg)], { - cwd: root, - nothrow: true, + return new Promise<{ code: number; stdout: Buffer; stderr: Buffer }>((resolve) => { + const proc = spawn(process.execPath, [worker, JSON.stringify(msg)], { + cwd: root, + }) + + const stdout: Buffer[] = [] + const stderr: Buffer[] = [] + + proc.stdout?.on("data", (data) => stdout.push(Buffer.from(data))) + proc.stderr?.on("data", (data) => stderr.push(Buffer.from(data))) + + proc.on("close", (code) => { + resolve({ + code: code ?? 1, + stdout: Buffer.concat(stdout), + stderr: Buffer.concat(stderr), + }) + }) }) } -function spawn(msg: Msg) { - return Process.spawn([process.execPath, worker, JSON.stringify(msg)], { +function spawnWorker(msg: Msg) { + return spawn(process.execPath, [worker, JSON.stringify(msg)], { cwd: root, - stdin: "ignore", - stdout: "pipe", - stderr: "pipe", + stdio: ["ignore", "pipe", "pipe"], }) } +function stopWorker(proc: ReturnType) { + if (proc.exitCode !== null || proc.signalCode !== null) return Promise.resolve() + + if (process.platform !== "win32" || !proc.pid) { + proc.kill() + return Promise.resolve() + } + + return new Promise((resolve) => { + const killProc = spawn("taskkill", ["/pid", String(proc.pid), "/T", "/F"]) + killProc.on("close", () => { + proc.kill() + resolve() + }) + }) +} + +async function readJson(p: string): Promise { + return JSON.parse(await fs.readFile(p, "utf8")) +} + describe("util.flock", () => { test("enforces mutual exclusion under process contention", async () => { await using tmp = await tmpdir() @@ -104,7 +147,7 @@ describe("util.flock", () => { const dir = path.join(tmp.path, "locks") const key = "flock:timeout" const ready = path.join(tmp.path, "ready") - const proc = spawn({ + const proc = spawnWorker({ key, dir, ready, @@ -131,8 +174,8 @@ describe("util.flock", () => { expect(seen.length).toBeGreaterThan(0) expect(seen.every((x) => x === key)).toBe(true) } finally { - await Process.stop(proc).catch(() => undefined) - await proc.exited.catch(() => undefined) + await stopWorker(proc).catch(() => undefined) + await new Promise((resolve) => proc.on("close", resolve)) } }, 15_000) @@ -141,7 +184,7 @@ describe("util.flock", () => { const dir = path.join(tmp.path, "locks") const key = "flock:crash" const ready = path.join(tmp.path, "ready") - const proc = spawn({ + const proc = spawnWorker({ key, dir, ready, @@ -151,8 +194,8 @@ describe("util.flock", () => { }) await wait(ready, 5_000) - await Process.stop(proc) - await proc.exited.catch(() => undefined) + await stopWorker(proc) + await new Promise((resolve) => proc.on("close", resolve)) let hit = false await Flock.withLock( @@ -276,7 +319,7 @@ describe("util.flock", () => { await Flock.withLock( key, async () => { - const json = await Filesystem.readJson<{ + const json = await readJson<{ token?: unknown pid?: unknown hostname?: unknown @@ -324,7 +367,7 @@ describe("util.flock", () => { const err = await Flock.withLock( key, async () => { - const json = await Filesystem.readJson<{ token?: string }>(meta) + const json = await readJson<{ token?: string }>(meta) json.token = "tampered" await fs.writeFile(meta, JSON.stringify(json, null, 2)) }, diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 0000000000..ff9886313a --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@tsconfig/bun/tsconfig.json", + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "@opentui/solid", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "types": [], + "noUncheckedIndexedAccess": false, + "customConditions": ["browser"], + "paths": { + "@/*": ["./src/*"], + "@tui/*": ["./src/cli/cmd/tui/*"] + }, + "plugins": [ + { + "name": "@effect/language-service", + "transform": "@effect/language-service/transform", + "namespaceImportPackages": ["effect", "@effect/*"] + } + ] + } +} diff --git a/packages/slack/package.json b/packages/slack/package.json index 62364bd149..9239ac5727 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.4.4", + "version": "1.4.6", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 760f7a1ec3..21974e3ec7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.4.4", + "version": "1.4.6", "type": "module", "license": "MIT", "exports": { @@ -44,7 +44,7 @@ "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", - "@opencode-ai/util": "workspace:*", + "@opencode-ai/shared": "workspace:*", "@pierre/diffs": "catalog:", "@shikijs/transformers": "3.9.2", "@solid-primitives/bounds": "0.1.3", diff --git a/packages/ui/src/components/accordion.css b/packages/ui/src/components/accordion.css index b4d6323d0d..cebb887f1f 100644 --- a/packages/ui/src/components/accordion.css +++ b/packages/ui/src/components/accordion.css @@ -51,10 +51,10 @@ line-height: var(--line-height-large); /* 166.667% */ letter-spacing: var(--letter-spacing-normal); - &:hover { + &:hover:not([data-disabled]) { background-color: var(--surface-base-hover); } - &:active { + &:active:not([data-disabled]) { background-color: var(--surface-base-active); } &:focus-visible { diff --git a/packages/ui/src/components/file.tsx b/packages/ui/src/components/file.tsx index b78f0bae44..51c2892737 100644 --- a/packages/ui/src/components/file.tsx +++ b/packages/ui/src/components/file.tsx @@ -1,4 +1,4 @@ -import { sampledChecksum } from "@opencode-ai/util/encode" +import { sampledChecksum } from "@opencode-ai/shared/util/encode" import { DEFAULT_VIRTUAL_FILE_METRICS, type DiffLineAnnotation, diff --git a/packages/ui/src/components/line-comment.tsx b/packages/ui/src/components/line-comment.tsx index 26e763bb3e..e20da5a8d3 100644 --- a/packages/ui/src/components/line-comment.tsx +++ b/packages/ui/src/components/line-comment.tsx @@ -1,5 +1,5 @@ import { useFilteredList } from "@opencode-ai/ui/hooks" -import { getDirectory, getFilename } from "@opencode-ai/util/path" +import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" import { createSignal, For, onMount, Show, splitProps, type JSX } from "solid-js" import { Button } from "./button" import { FileIcon } from "./file-icon" diff --git a/packages/ui/src/components/markdown.tsx b/packages/ui/src/components/markdown.tsx index ceab10df98..f3037da8bc 100644 --- a/packages/ui/src/components/markdown.tsx +++ b/packages/ui/src/components/markdown.tsx @@ -2,7 +2,7 @@ import { useMarked } from "../context/marked" import { useI18n } from "../context/i18n" import DOMPurify from "dompurify" import morphdom from "morphdom" -import { checksum } from "@opencode-ai/util/encode" +import { checksum } from "@opencode-ai/shared/util/encode" import { ComponentProps, createEffect, createResource, createSignal, onCleanup, splitProps } from "solid-js" import { isServer } from "solid-js/web" import { stream } from "./markdown-stream" diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 02bd80ac9c..48444cd017 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -46,8 +46,8 @@ import { Checkbox } from "./checkbox" import { DiffChanges } from "./diff-changes" import { Markdown } from "./markdown" import { ImagePreview } from "./image-preview" -import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/util/path" -import { checksum } from "@opencode-ai/util/encode" +import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/shared/util/path" +import { checksum } from "@opencode-ai/shared/util/encode" import { Tooltip } from "./tooltip" import { IconButton } from "./icon-button" import { Spinner } from "./spinner" diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 4a7205a5dc..bb19d099e0 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -11,8 +11,8 @@ import { Tooltip } from "./tooltip" import { ScrollView } from "./scroll-view" import { useFileComponent } from "../context/file" import { useI18n } from "../context/i18n" -import { getDirectory, getFilename } from "@opencode-ai/util/path" -import { checksum } from "@opencode-ai/util/encode" +import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" +import { checksum } from "@opencode-ai/shared/util/encode" import { createEffect, createMemo, For, Match, onCleanup, Show, Switch, untrack, type JSX } from "solid-js" import { createStore } from "solid-js/store" import { type FileContent, type SnapshotFileDiff, type VcsFileDiff } from "@opencode-ai/sdk/v2" @@ -388,6 +388,9 @@ export const SessionReview = (props: SessionReviewProps) => { let wrapper: HTMLDivElement | undefined const file = diff.file + // binary files have empty diffs that we can't render + const diffCanRender = () => diff.additions !== 0 && diff.deletions !== 0 + const expanded = createMemo(() => open().includes(file)) const mounted = createMemo(() => expanded() && (!!store.visible[file] || pinned(file))) const force = () => !!store.force[file] @@ -496,14 +499,14 @@ export const SessionReview = (props: SessionReviewProps) => { return ( - +
    @@ -512,7 +515,7 @@ export const SessionReview = (props: SessionReviewProps) => { {`\u202A${getDirectory(file)}\u202C`} {getFilename(file)} - +
    diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index e891a6febe..6d43a575a7 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -8,8 +8,8 @@ import type { SessionStatus } from "@opencode-ai/sdk/v2" import { useData } from "../context" import { useFileComponent } from "../context/file" -import { Binary } from "@opencode-ai/util/binary" -import { getDirectory, getFilename } from "@opencode-ai/util/path" +import { Binary } from "@opencode-ai/shared/util/binary" +import { getDirectory, getFilename } from "@opencode-ai/shared/util/path" import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js" import { createStore } from "solid-js/store" import { Dynamic } from "solid-js/web" diff --git a/packages/ui/src/components/tooltip.tsx b/packages/ui/src/components/tooltip.tsx index ada597d0f2..4366651d2b 100644 --- a/packages/ui/src/components/tooltip.tsx +++ b/packages/ui/src/components/tooltip.tsx @@ -99,6 +99,8 @@ export function Tooltip(props: TooltipProps) { onCleanup(() => obs.disconnect()) }) + let justClickedTrigger = false + return ( {local.children} @@ -112,6 +114,10 @@ export function Tooltip(props: TooltipProps) { onOpenChange={(open) => { if (local.forceOpen) return if (state.block && open) return + if (justClickedTrigger) { + justClickedTrigger = false + return + } setState("open", open) }} > @@ -137,6 +143,12 @@ export function Tooltip(props: TooltipProps) { data-force-open={local.forceOpen} class={local.contentClass} style={local.contentStyle} + onPointerDownOutside={(e) => { + if (ref === e.target || (e.target instanceof Node && ref?.contains(e.target))) { + justClickedTrigger = true + } + e.preventDefault() + }} > {local.value} {/* */} diff --git a/packages/ui/src/styles/base.css b/packages/ui/src/styles/base.css index b5604ad619..a032f9ea2d 100644 --- a/packages/ui/src/styles/base.css +++ b/packages/ui/src/styles/base.css @@ -82,7 +82,7 @@ a { cursor: default; } -*[data-tauri-drag-region] { +#root:not([aria-hidden]) *[data-tauri-drag-region] { app-region: drag; } diff --git a/packages/util/package.json b/packages/util/package.json deleted file mode 100644 index 10f7922f4f..0000000000 --- a/packages/util/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "@opencode-ai/util", - "version": "1.4.4", - "private": true, - "type": "module", - "license": "MIT", - "exports": { - "./*": "./src/*.ts" - }, - "scripts": { - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "zod": "catalog:" - }, - "devDependencies": { - "typescript": "catalog:", - "@types/bun": "catalog:" - } -} diff --git a/packages/util/tsconfig.json b/packages/util/tsconfig.json deleted file mode 100644 index 528dcd91d9..0000000000 --- a/packages/util/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "bundler", - "skipLibCheck": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "allowJs": true, - "noEmit": true, - "strict": true, - "isolatedModules": true - } -} diff --git a/packages/web/package.json b/packages/web/package.json index fe8d6e17cd..5ca11ea296 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -2,7 +2,7 @@ "name": "@opencode-ai/web", "type": "module", "license": "MIT", - "version": "1.4.4", + "version": "1.4.6", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/packages/web/src/content/docs/ar/go.mdx b/packages/web/src/content/docs/ar/go.mdx index 34ae96eaaa..655749eeae 100644 --- a/packages/web/src/content/docs/ar/go.mdx +++ b/packages/web/src/content/docs/ar/go.mdx @@ -59,6 +59,8 @@ OpenCode Go حاليًا في المرحلة التجريبية. - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** قد تتغير قائمة النماذج مع استمرارنا في اختبار نماذج جديدة وإضافتها. @@ -77,17 +79,25 @@ OpenCode Go حاليًا في المرحلة التجريبية. يوضح الجدول أدناه عددًا تقديريًا للطلبات بناءً على أنماط استخدام Go المعتادة: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| الطلبات لكل 5 ساعات | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| الطلبات في الأسبوع | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| الطلبات في الشهر | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | الطلبات لكل 5 ساعات | الطلبات في الأسبوع | الطلبات في الشهر | +| ------------ | ------------------- | ------------------ | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | تستند التقديرات إلى متوسطات أنماط الطلبات المرصودة: - GLM-5/5.1 — ‏700 input، و52,000 cached، و150 output tokens لكل طلب - Kimi K2.5 — ‏870 input، و55,000 cached، و200 output tokens لكل طلب - MiniMax M2.7/M2.5 — ‏300 input، و55,000 cached، و125 output tokens لكل طلب +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — ‏350 input، و41,000 cached، و250 output tokens لكل طلب - MiMo-V2-Omni — ‏1000 input، و60,000 cached، و140 output tokens لكل طلب @@ -120,6 +130,8 @@ OpenCode Go حاليًا في المرحلة التجريبية. | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | يستخدم [model id](/docs/config/#models) في إعدادات OpenCode لديك التنسيق `opencode-go/`. على سبيل المثال، بالنسبة إلى Kimi K2.5، ستستخدم `opencode-go/kimi-k2.5` في إعداداتك. diff --git a/packages/web/src/content/docs/ar/zen.mdx b/packages/web/src/content/docs/ar/zen.mdx index c7cfe57b74..7a3931c85c 100644 --- a/packages/web/src/content/docs/ar/zen.mdx +++ b/packages/web/src/content/docs/ar/zen.mdx @@ -84,6 +84,8 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -92,7 +94,6 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | يستخدم [معرّف النموذج](/docs/config/#models) في إعدادات OpenCode الصيغة `opencode/`. على سبيل المثال، بالنسبة إلى GPT 5.3 Codex، ستستخدم `opencode/gpt-5.3-codex` في إعداداتك. @@ -118,7 +119,8 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -166,7 +168,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - MiMo V2 Pro Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - MiMo V2 Omni Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. -- Qwen3.6 Plus Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Nemotron 3 Super Free متاح على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. - Big Pickle نموذج خفي ومتاح مجانا على OpenCode لفترة محدودة. يستخدم الفريق هذه الفترة لجمع الملاحظات وتحسين النموذج. @@ -212,7 +213,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - MiMo V2 Pro Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - MiMo V2 Omni Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. -- Qwen3.6 Plus Free: خلال فترته المجانية، قد تُستخدم البيانات المجمعة لتحسين النموذج. - Nemotron 3 Super Free (نقاط نهاية NVIDIA المجانية): يُقدَّم بموجب [شروط خدمة النسخة التجريبية من واجهة NVIDIA API](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). للاستخدام التجريبي فقط، وليس للإنتاج أو البيانات الحساسة. تقوم NVIDIA بتسجيل المطالبات والمخرجات لتحسين نماذجها وخدماتها. لا ترسل بيانات شخصية أو سرية. - OpenAI APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: يتم الاحتفاظ بالطلبات لمدة 30 يوما وفقا لـ [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/bs/go.mdx b/packages/web/src/content/docs/bs/go.mdx index 1e4371c12d..3dabedf6a3 100644 --- a/packages/web/src/content/docs/bs/go.mdx +++ b/packages/web/src/content/docs/bs/go.mdx @@ -69,6 +69,8 @@ Trenutna lista modela uključuje: - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** Lista modela se može mijenjati dok testiramo i dodajemo nove. @@ -87,17 +89,25 @@ Ograničenja su definisana u dolarskoj vrijednosti. To znači da vaš stvarni br Tabela ispod pruža procijenjeni broj zahtjeva na osnovu tipičnih obrazaca korištenja Go pretplate: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ------------------ | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| zahtjeva na 5 sati | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| zahtjeva sedmično | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| zahtjeva mjesečno | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | zahtjeva na 5 sati | zahtjeva sedmično | zahtjeva mjesečno | +| ------------ | ------------------ | ----------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Procjene se zasnivaju na zapaženim prosječnim obrascima zahtjeva: - GLM-5/5.1 — 700 ulaznih (input), 52,000 keširanih, 150 izlaznih (output) tokena po zahtjevu - Kimi K2.5 — 870 ulaznih, 55,000 keširanih, 200 izlaznih tokena po zahtjevu - MiniMax M2.7/M2.5 — 300 ulaznih, 55,000 keširanih, 125 izlaznih tokena po zahtjevu +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 ulaznih, 41,000 keširanih, 250 izlaznih tokena po zahtjevu - MiMo-V2-Omni — 1000 ulaznih, 60,000 keširanih, 140 izlaznih tokena po zahtjevu @@ -132,6 +142,8 @@ Također možete pristupiti Go modelima putem sljedećih API endpointa. | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [Model id](/docs/config/#models) u vašoj OpenCode konfiguraciji koristi format `opencode-go/`. Na primjer, za Kimi K2.5, koristili biste diff --git a/packages/web/src/content/docs/bs/zen.mdx b/packages/web/src/content/docs/bs/zen.mdx index 9edfdddd17..76a25e5f65 100644 --- a/packages/web/src/content/docs/bs/zen.mdx +++ b/packages/web/src/content/docs/bs/zen.mdx @@ -89,6 +89,8 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -97,7 +99,6 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa. | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) u vašoj OpenCode konfiguraciji koristi format @@ -125,7 +126,8 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**. | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -173,7 +175,6 @@ Besplatni modeli: - MiniMax M2.5 Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - MiMo V2 Pro Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - MiMo V2 Omni Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. -- Qwen3.6 Plus Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Nemotron 3 Super Free je dostupan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. - Big Pickle je stealth model koji je besplatan na OpenCode ograničeno vrijeme. Tim koristi ovo vrijeme da prikupi povratne informacije i poboljša model. @@ -224,7 +225,6 @@ i ne koriste vaše podatke za treniranje modela, uz sljedeće izuzetke: - MiniMax M2.5 Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - MiMo V2 Pro Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - MiMo V2 Omni Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. -- Qwen3.6 Plus Free: Tokom besplatnog perioda, prikupljeni podaci mogu se koristiti za poboljšanje modela. - Nemotron 3 Super Free (besplatni NVIDIA endpointi): Dostupan je prema [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Samo za probnu upotrebu, nije za produkciju niti osjetljive podatke. NVIDIA bilježi promptove i izlaze radi poboljšanja svojih modela i usluga. Nemojte slati lične ili povjerljive podatke. - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/da/go.mdx b/packages/web/src/content/docs/da/go.mdx index 9cc45443ee..54f17bd77b 100644 --- a/packages/web/src/content/docs/da/go.mdx +++ b/packages/web/src/content/docs/da/go.mdx @@ -69,6 +69,8 @@ Den nuværende liste over modeller inkluderer: - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** Listen over modeller kan ændre sig, efterhånden som vi tester og tilføjer nye. @@ -87,17 +89,25 @@ Grænserne er defineret i dollarværdi. Det betyder, at dit faktiske antal anmod Tabellen nedenfor giver et estimeret antal anmodninger baseret på typiske Go-forbrugsmønstre: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ----------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| anmodninger pr. 5 timer | 880 | 1.150 | 1.850 | 1.290 | 2.150 | 14.000 | 20.000 | -| anmodninger pr. uge | 2.150 | 2.880 | 4.630 | 3.225 | 5.450 | 35.000 | 50.000 | -| anmodninger pr. måned | 4.300 | 5.750 | 9.250 | 6.450 | 10.900 | 70.000 | 100.000 | +| Model | anmodninger pr. 5 timer | anmodninger pr. uge | anmodninger pr. måned | +| ------------ | ----------------------- | ------------------- | --------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Estimaterne er baseret på observerede gennemsnitlige anmodningsmønstre: - GLM-5/5.1 — 700 input, 52.000 cachelagrede, 150 output-tokens pr. anmodning - Kimi K2.5 — 870 input, 55.000 cachelagrede, 200 output-tokens pr. anmodning - MiniMax M2.7/M2.5 — 300 input, 55.000 cachelagrede, 125 output-tokens pr. anmodning +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 input, 41.000 cachelagrede, 250 output-tokens pr. anmodning - MiMo-V2-Omni — 1000 input, 60.000 cachelagrede, 140 output-tokens pr. anmodning @@ -132,6 +142,8 @@ Du kan også få adgang til Go-modeller gennem følgende API-endpoints. | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Dit [model id](/docs/config/#models) i din OpenCode config bruger formatet `opencode-go/`. For eksempel for Kimi K2.5, vil du diff --git a/packages/web/src/content/docs/da/zen.mdx b/packages/web/src/content/docs/da/zen.mdx index 2342930669..146fa02803 100644 --- a/packages/web/src/content/docs/da/zen.mdx +++ b/packages/web/src/content/docs/da/zen.mdx @@ -89,6 +89,8 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -97,7 +99,6 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints. | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) i din OpenCode-konfiguration @@ -125,7 +126,8 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**. | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -173,7 +175,6 @@ De gratis modeller: - MiniMax M2.5 Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - MiMo V2 Pro Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - MiMo V2 Omni Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. -- Qwen3.6 Plus Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Nemotron 3 Super Free er tilgængelig på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. - Big Pickle er en stealth-model, som er gratis på OpenCode i en begrænset periode. Teamet bruger denne tid til at indsamle feedback og forbedre modellen. @@ -222,7 +223,6 @@ Alle vores modeller hostes i US. Vores udbydere følger en nul-opbevaringspoliti - MiniMax M2.5 Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - MiMo V2 Pro Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - MiMo V2 Omni Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. -- Qwen3.6 Plus Free: I den gratis periode kan indsamlede data blive brugt til at forbedre modellen. - Nemotron 3 Super Free (gratis NVIDIA-endpoints): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun til prøvebrug, ikke til produktion eller følsomme data. Prompts og outputs logges af NVIDIA for at forbedre deres modeller og tjenester. Indsend ikke personlige eller fortrolige data. - OpenAI APIs: Anmodninger opbevares i 30 dage i overensstemmelse med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Anmodninger opbevares i 30 dage i overensstemmelse med [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/de/go.mdx b/packages/web/src/content/docs/de/go.mdx index 95237ea82a..0c5ec931ae 100644 --- a/packages/web/src/content/docs/de/go.mdx +++ b/packages/web/src/content/docs/de/go.mdx @@ -61,6 +61,8 @@ Die aktuelle Liste der Modelle umfasst: - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** Die Liste der Modelle kann sich ändern, während wir neue testen und hinzufügen. @@ -79,17 +81,25 @@ Limits sind in Dollarwerten definiert. Das bedeutet, dass die tatsächliche Anza Die folgende Tabelle zeigt eine geschätzte Anzahl von Anfragen basierend auf typischen Go-Nutzungsmustern: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ---------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| Anfragen pro 5 Stunden | 880 | 1.150 | 1.850 | 1.290 | 2.150 | 14.000 | 20.000 | -| Anfragen pro Woche | 2.150 | 2.880 | 4.630 | 3.225 | 5.450 | 35.000 | 50.000 | -| Anfragen pro Monat | 4.300 | 5.750 | 9.250 | 6.450 | 10.900 | 70.000 | 100.000 | +| Model | Anfragen pro 5 Stunden | Anfragen pro Woche | Anfragen pro Monat | +| ------------ | ---------------------- | ------------------ | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Die Schätzungen basieren auf beobachteten durchschnittlichen Anfragemustern: - GLM-5/5.1 — 700 Input-, 52.000 Cached-, 150 Output-Tokens pro Anfrage - Kimi K2.5 — 870 Input-, 55.000 Cached-, 200 Output-Tokens pro Anfrage - MiniMax M2.7/M2.5 — 300 Input-, 55.000 Cached-, 125 Output-Tokens pro Anfrage +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 Input-, 41.000 Cached-, 250 Output-Tokens pro Anfrage - MiMo-V2-Omni — 1.000 Input-, 60.000 Cached-, 140 Output-Tokens pro Anfrage @@ -122,6 +132,8 @@ Du kannst auf die Go-Modelle auch über die folgenden API-Endpunkte zugreifen. | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Die [Modell-ID](/docs/config/#models) in deiner OpenCode Config verwendet das Format `opencode-go/`. Für Kimi K2.5 würdest du beispielsweise `opencode-go/kimi-k2.5` in deiner Config verwenden. diff --git a/packages/web/src/content/docs/de/zen.mdx b/packages/web/src/content/docs/de/zen.mdx index e7e2d4e9b2..bc9438672b 100644 --- a/packages/web/src/content/docs/de/zen.mdx +++ b/packages/web/src/content/docs/de/zen.mdx @@ -80,6 +80,8 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -88,7 +90,6 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen. | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | Die [Model-ID](/docs/config/#models) in deiner OpenCode-Konfiguration verwendet das Format `opencode/`. Für GPT 5.3 Codex würdest du zum Beispiel `opencode/gpt-5.3-codex` in deiner Konfiguration verwenden. @@ -114,7 +115,8 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -162,7 +164,6 @@ Die kostenlosen Modelle: - MiniMax M2.5 Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - MiMo V2 Pro Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - MiMo V2 Omni Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. -- Qwen3.6 Plus Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Nemotron 3 Super Free ist für begrenzte Zeit auf OpenCode verfügbar. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. - Big Pickle ist ein Stealth-Modell, das für begrenzte Zeit kostenlos auf OpenCode verfügbar ist. Das Team nutzt diese Zeit, um Feedback zu sammeln und das Modell zu verbessern. @@ -208,7 +209,6 @@ Alle unsere Modelle werden in den USA gehostet. Unsere Provider folgen einer Zer - MiniMax M2.5 Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - MiMo V2 Pro Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - MiMo V2 Omni Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. -- Qwen3.6 Plus Free: Während des kostenlosen Zeitraums können gesammelte Daten zur Verbesserung des Modells verwendet werden. - Nemotron 3 Super Free (kostenlose NVIDIA-Endpunkte): Bereitgestellt gemäß den [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Nur für Testzwecke, nicht für Produktion oder sensible Daten. Eingaben und Ausgaben werden von NVIDIA protokolliert, um seine Modelle und Dienste zu verbessern. Übermitteln Sie keine personenbezogenen oder vertraulichen Daten. - OpenAI APIs: Anfragen werden in Übereinstimmung mit [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 30 Tage lang gespeichert. - Anthropic APIs: Anfragen werden in Übereinstimmung mit [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 30 Tage lang gespeichert. diff --git a/packages/web/src/content/docs/es/go.mdx b/packages/web/src/content/docs/es/go.mdx index 5d38ee9009..86ddbe81e8 100644 --- a/packages/web/src/content/docs/es/go.mdx +++ b/packages/web/src/content/docs/es/go.mdx @@ -69,6 +69,8 @@ La lista actual de modelos incluye: - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** La lista de modelos puede cambiar a medida que probamos y agregamos otros nuevos. @@ -87,17 +89,25 @@ Los límites se definen en valor en dólares. Esto significa que tu cantidad rea La siguiente tabla proporciona una cantidad estimada de peticiones basada en los patrones típicos de uso de Go: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ---------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| peticiones por 5 horas | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| peticiones por semana | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| peticiones por mes | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | peticiones por 5 horas | peticiones por semana | peticiones por mes | +| ------------ | ---------------------- | --------------------- | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Las estimaciones se basan en los patrones de peticiones promedio observados: - GLM-5/5.1 — 700 tokens de entrada, 52,000 en caché, 150 tokens de salida por petición - Kimi K2.5 — 870 tokens de entrada, 55,000 en caché, 200 tokens de salida por petición - MiniMax M2.7/M2.5 — 300 tokens de entrada, 55,000 en caché, 125 tokens de salida por petición +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 tokens de entrada, 41,000 en caché, 250 tokens de salida por petición - MiMo-V2-Omni — 1000 tokens de entrada, 60,000 en caché, 140 tokens de salida por petición @@ -132,6 +142,8 @@ También puedes acceder a los modelos de Go a través de los siguientes endpoint | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | El [ID del modelo](/docs/config/#models) en tu configuración de OpenCode usa el formato `opencode-go/`. Por ejemplo, para Kimi K2.5, usarías diff --git a/packages/web/src/content/docs/es/zen.mdx b/packages/web/src/content/docs/es/zen.mdx index a68871fb4c..6fc0fb13e1 100644 --- a/packages/web/src/content/docs/es/zen.mdx +++ b/packages/web/src/content/docs/es/zen.mdx @@ -89,6 +89,8 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -97,7 +99,6 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | El [identificador del modelo](/docs/config/#models) en tu configuración de OpenCode @@ -125,7 +126,8 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -173,7 +175,6 @@ Los modelos gratuitos: - MiniMax M2.5 Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - MiMo V2 Pro Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - MiMo V2 Omni Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. -- Qwen3.6 Plus Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Nemotron 3 Super Free está disponible en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. - Big Pickle es un modelo stealth que es gratuito en OpenCode por tiempo limitado. El equipo está usando este tiempo para recopilar comentarios y mejorar el modelo. @@ -222,7 +223,6 @@ Todos nuestros modelos están alojados en US. Nuestros proveedores siguen una po - MiniMax M2.5 Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - MiMo V2 Pro Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - MiMo V2 Omni Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. -- Qwen3.6 Plus Free: Durante su período gratuito, los datos recopilados pueden usarse para mejorar el modelo. - Nemotron 3 Super Free (endpoints gratuitos de NVIDIA): Se ofrece bajo los [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo para uso de prueba, no para producción ni datos sensibles. NVIDIA registra los prompts y las salidas para mejorar sus modelos y servicios. No envíes datos personales ni confidenciales. - OpenAI APIs: Las solicitudes se conservan durante 30 días de acuerdo con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Las solicitudes se conservan durante 30 días de acuerdo con [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/fr/go.mdx b/packages/web/src/content/docs/fr/go.mdx index 2b819438fc..b10cf9141e 100644 --- a/packages/web/src/content/docs/fr/go.mdx +++ b/packages/web/src/content/docs/fr/go.mdx @@ -59,6 +59,8 @@ La liste actuelle des modèles comprend : - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** La liste des modèles peut changer au fur et à mesure que nous en testons et en ajoutons de nouveaux. @@ -77,17 +79,25 @@ Les limites sont définies en valeur monétaire (dollars). Cela signifie que vot Le tableau ci-dessous fournit une estimation du nombre de requêtes basée sur des modèles d'utilisation typiques de Go : -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| --------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| requêtes par 5 heures | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| requêtes par semaine | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| requêtes par mois | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | requêtes par 5 heures | requêtes par semaine | requêtes par mois | +| ------------ | --------------------- | -------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Les estimations sont basées sur les modèles de requêtes moyens observés : - GLM-5/5.1 — 700 tokens en entrée, 52,000 en cache, 150 tokens en sortie par requête - Kimi K2.5 — 870 tokens en entrée, 55,000 en cache, 200 tokens en sortie par requête - MiniMax M2.7/M2.5 — 300 tokens en entrée, 55,000 en cache, 125 tokens en sortie par requête +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 tokens en entrée, 41,000 en cache, 250 tokens en sortie par requête - MiMo-V2-Omni — 1000 tokens en entrée, 60,000 en cache, 140 tokens en sortie par requête @@ -120,6 +130,8 @@ Vous pouvez également accéder aux modèles Go via les points de terminaison d' | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | L'[ID de modèle](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode-go/`. Par exemple, pour Kimi K2.5, vous utiliseriez `opencode-go/kimi-k2.5` dans votre configuration. diff --git a/packages/web/src/content/docs/fr/zen.mdx b/packages/web/src/content/docs/fr/zen.mdx index f1d76beae9..a71328a72e 100644 --- a/packages/web/src/content/docs/fr/zen.mdx +++ b/packages/web/src/content/docs/fr/zen.mdx @@ -80,6 +80,8 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -88,7 +90,6 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | Le [model id](/docs/config/#models) dans votre configuration OpenCode utilise le format `opencode/`. Par exemple, pour GPT 5.3 Codex, vous utiliseriez `opencode/gpt-5.3-codex` dans votre configuration. @@ -114,7 +115,8 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -162,7 +164,6 @@ Les modèles gratuits : - MiniMax M2.5 Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - MiMo V2 Pro Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - MiMo V2 Omni Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. -- Qwen3.6 Plus Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Nemotron 3 Super Free est disponible sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. - Big Pickle est un modèle stealth gratuit sur OpenCode pour une durée limitée. L'équipe utilise cette période pour recueillir des retours et améliorer le modèle. @@ -208,7 +209,6 @@ Tous nos modèles sont hébergés aux US. Nos fournisseurs suivent une politique - MiniMax M2.5 Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - MiMo V2 Pro Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - MiMo V2 Omni Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. -- Qwen3.6 Plus Free : Pendant sa période gratuite, les données collectées peuvent être utilisées pour améliorer le modèle. - Nemotron 3 Super Free (endpoints NVIDIA gratuits) : Fourni dans le cadre des [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Réservé à un usage d'essai, pas à la production ni aux données sensibles. Les prompts et les sorties sont journalisés par NVIDIA pour améliorer ses modèles et services. N'envoyez pas de données personnelles ou confidentielles. - OpenAI APIs : Les requêtes sont conservées pendant 30 jours conformément à [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs : Les requêtes sont conservées pendant 30 jours conformément à [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/go.mdx b/packages/web/src/content/docs/go.mdx index df65aa76a5..b8d608a4af 100644 --- a/packages/web/src/content/docs/go.mdx +++ b/packages/web/src/content/docs/go.mdx @@ -70,6 +70,8 @@ The current list of models includes: - **MiMo-V2-Omni** - **MiniMax M2.5** - **MiniMax M2.7** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** The list of models may change as we test and add new ones. @@ -83,15 +85,21 @@ OpenCode Go includes the following limits: - **Weekly limit** — $30 of usage - **Monthly limit** — $60 of usage -Limits are defined in dollar value. This means your actual request count depends on the model you use. Cheaper models like MiniMax M2.5 allow for more requests, while higher-cost models like GLM-5.1 allow for fewer. +Limits are defined in dollar value. This means your actual request count depends on the model you use. Cheaper models like Qwen3.5 Plus allow for more requests, while higher-cost models like GLM-5.1 allow for fewer. The table below provides an estimated request count based on typical Go usage patterns: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| requests per 5 hour | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| requests per week | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| requests per month | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | requests per 5 hour | requests per week | requests per month | +| ------------ | ------------------- | ----------------- | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Estimates are based on observed average request patterns: @@ -100,6 +108,8 @@ Estimates are based on observed average request patterns: - MiniMax M2.7/M2.5 — 300 input, 55,000 cached, 125 output tokens per request - MiMo-V2-Pro — 350 input, 41,000 cached, 250 output tokens per request - MiMo-V2-Omni — 1000 input, 60,000 cached, 140 output tokens per request +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request You can track your current usage in the **
    console**. @@ -132,6 +142,8 @@ You can also access Go models through the following API endpoints. | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | The [model id](/docs/config/#models) in your OpenCode config uses the format `opencode-go/`. For example, for Kimi K2.5, you would diff --git a/packages/web/src/content/docs/it/go.mdx b/packages/web/src/content/docs/it/go.mdx index e4b95d2286..f90dce5094 100644 --- a/packages/web/src/content/docs/it/go.mdx +++ b/packages/web/src/content/docs/it/go.mdx @@ -67,6 +67,8 @@ L'elenco attuale dei modelli include: - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** L'elenco dei modelli potrebbe cambiare man mano che ne testiamo e aggiungiamo di nuovi. @@ -85,17 +87,25 @@ I limiti sono definiti in valore in dollari. Questo significa che il conteggio e La tabella seguente fornisce una stima del conteggio delle richieste in base a pattern di utilizzo tipici di Go: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| --------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| richieste ogni 5 ore | 880 | 1.150 | 1.850 | 1.290 | 2.150 | 14.000 | 20.000 | -| richieste a settimana | 2.150 | 2.880 | 4.630 | 3.225 | 5.450 | 35.000 | 50.000 | -| richieste al mese | 4.300 | 5.750 | 9.250 | 6.450 | 10.900 | 70.000 | 100.000 | +| Model | richieste ogni 5 ore | richieste a settimana | richieste al mese | +| ------------ | -------------------- | --------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Le stime si basano sui pattern medi di richieste osservati: - GLM-5/5.1 — 700 di input, 52.000 in cache, 150 token di output per richiesta - Kimi K2.5 — 870 di input, 55.000 in cache, 200 token di output per richiesta - MiniMax M2.7/M2.5 — 300 di input, 55.000 in cache, 125 token di output per richiesta +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 di input, 41.000 in cache, 250 token di output per richiesta - MiMo-V2-Omni — 1000 di input, 60.000 in cache, 140 token di output per richiesta @@ -130,6 +140,8 @@ Puoi anche accedere ai modelli Go tramite i seguenti endpoint API. | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | Il [model id](/docs/config/#models) nella tua OpenCode config utilizza il formato `opencode-go/`. Ad esempio, per Kimi K2.5, useresti diff --git a/packages/web/src/content/docs/it/zen.mdx b/packages/web/src/content/docs/it/zen.mdx index b8bb388efb..cdc2c9a45b 100644 --- a/packages/web/src/content/docs/it/zen.mdx +++ b/packages/web/src/content/docs/it/zen.mdx @@ -89,6 +89,8 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -97,7 +99,6 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API. | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | Il [model id](/docs/config/#models) nella config di OpenCode @@ -125,7 +126,8 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**. | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -173,7 +175,6 @@ I modelli gratuiti: - MiniMax M2.5 Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - MiMo V2 Pro Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - MiMo V2 Omni Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. -- Qwen3.6 Plus Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Nemotron 3 Super Free è disponibile su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. - Big Pickle è un modello stealth che è gratuito su OpenCode per un periodo limitato. Il team usa questo periodo per raccogliere feedback e migliorare il modello. @@ -222,7 +223,6 @@ Tutti i nostri modelli sono ospitati negli US. I nostri provider seguono una pol - MiniMax M2.5 Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - MiMo V2 Pro Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - MiMo V2 Omni Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. -- Qwen3.6 Plus Free: durante il periodo gratuito, i dati raccolti possono essere usati per migliorare il modello. - Nemotron 3 Super Free (endpoint NVIDIA gratuiti): fornito secondo i [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Solo per uso di prova, non per produzione o dati sensibili. NVIDIA registra prompt e output per migliorare i propri modelli e servizi. Non inviare dati personali o riservati. - OpenAI APIs: le richieste vengono conservate per 30 giorni in conformità con [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: le richieste vengono conservate per 30 giorni in conformità con [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/ja/go.mdx b/packages/web/src/content/docs/ja/go.mdx index 35e2775b49..01f1d6390b 100644 --- a/packages/web/src/content/docs/ja/go.mdx +++ b/packages/web/src/content/docs/ja/go.mdx @@ -59,6 +59,8 @@ OpenCode Goをサブスクライブできるのは、1つのワークスペー - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** 新しいモデルをテストして追加するにつれて、モデルのリストは変更される場合があります。 @@ -77,17 +79,25 @@ OpenCode Goには以下の制限が含まれています: 以下の表は、一般的なGoの利用パターンに基づいた推定リクエスト数を示しています: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ------------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| 5時間あたりのリクエスト数 | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| 週間リクエスト数 | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| 月間リクエスト数 | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | 5時間あたりのリクエスト数 | 週間リクエスト数 | 月間リクエスト数 | +| ------------ | ------------------------- | ---------------- | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | 推定値は、観測された平均的なリクエストパターンに基づいています: - GLM-5/5.1 — リクエストあたり 入力 700トークン、キャッシュ 52,000トークン、出力 150トークン - Kimi K2.5 — リクエストあたり 入力 870トークン、キャッシュ 55,000トークン、出力 200トークン - MiniMax M2.7/M2.5 — リクエストあたり 入力 300トークン、キャッシュ 55,000トークン、出力 125トークン +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — リクエストあたり 入力 350トークン、キャッシュ 41,000トークン、出力 250トークン - MiMo-V2-Omni — リクエストあたり 入力 1000トークン、キャッシュ 60,000トークン、出力 140トークン @@ -120,6 +130,8 @@ Zen残高にクレジットがある場合は、コンソールで**Use balance* | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode設定の[model id](/docs/config/#models)は、`opencode-go/`という形式を使用します。たとえば、Kimi K2.5の場合は、設定で`opencode-go/kimi-k2.5`を使用します。 diff --git a/packages/web/src/content/docs/ja/zen.mdx b/packages/web/src/content/docs/ja/zen.mdx index f0536c3895..b8da1308c1 100644 --- a/packages/web/src/content/docs/ja/zen.mdx +++ b/packages/web/src/content/docs/ja/zen.mdx @@ -80,6 +80,8 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -88,7 +90,6 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動 | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode 設定で使う [model id](/docs/config/#models) は `opencode/` 形式です。たとえば、GPT 5.3 Codex では設定に `opencode/gpt-5.3-codex` を使用します。 @@ -114,7 +115,8 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -162,7 +164,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - MiMo V2 Pro Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - MiMo V2 Omni Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 -- Qwen3.6 Plus Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Nemotron 3 Super Free は期間限定で OpenCode で利用できます。チームはこの期間中にフィードバックを集め、モデルを改善しています。 - Big Pickle はステルスモデルで、期間限定で OpenCode で無料提供されています。チームはこの期間中にフィードバックを集め、モデルを改善しています。 @@ -208,7 +209,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - MiMo V2 Pro Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - MiMo V2 Omni Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 -- Qwen3.6 Plus Free: 無料提供期間中、収集されたデータがモデル改善に使われる場合があります。 - Nemotron 3 Super Free(NVIDIA の無料エンドポイント): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) に基づいて提供されます。試用専用であり、本番環境や機密性の高いデータには使用しないでください。プロンプトと出力は、NVIDIA が自社のモデルとサービスを改善するために記録します。個人情報や機密データは送信しないでください。 - OpenAI APIs: リクエストは [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) に従って 30 日間保持されます。 - Anthropic APIs: リクエストは [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) に従って 30 日間保持されます。 diff --git a/packages/web/src/content/docs/ko/go.mdx b/packages/web/src/content/docs/ko/go.mdx index 25d4c27605..9af7541e10 100644 --- a/packages/web/src/content/docs/ko/go.mdx +++ b/packages/web/src/content/docs/ko/go.mdx @@ -59,6 +59,8 @@ workspace당 한 명의 멤버만 OpenCode Go를 구독할 수 있습니다. - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** 새로운 모델을 테스트하고 추가함에 따라 이 목록은 변경될 수 있습니다. @@ -77,17 +79,25 @@ OpenCode Go에는 다음과 같은 한도가 포함됩니다. 아래 표는 일반적인 Go 사용 패턴을 기준으로 한 예상 요청 횟수를 보여줍니다. -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ----------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| 5시간당 요청 횟수 | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| 주간 요청 횟수 | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| 월간 요청 횟수 | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | 5시간당 요청 횟수 | 주간 요청 횟수 | 월간 요청 횟수 | +| ------------ | ----------------- | -------------- | -------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | 예상치는 관찰된 평균 요청 패턴을 기준으로 합니다. - GLM-5/5.1 — 요청당 입력 700, 캐시 52,000, 출력 토큰 150 - Kimi K2.5 — 요청당 입력 870, 캐시 55,000, 출력 토큰 200 - MiniMax M2.7/M2.5 — 요청당 입력 300, 캐시 55,000, 출력 토큰 125 +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 요청당 입력 350, 캐시 41,000, 출력 토큰 250 - MiMo-V2-Omni — 요청당 입력 1000, 캐시 60,000, 출력 토큰 140 @@ -120,6 +130,8 @@ Zen 잔액에 크레딧도 있다면, console에서 **Use balance** 옵션을 | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode config의 [model id](/docs/config/#models)는 `opencode-go/` 형식을 사용합니다. 예를 들어 Kimi K2.5의 경우 config에서 `opencode-go/kimi-k2.5`를 사용하면 됩니다. diff --git a/packages/web/src/content/docs/ko/zen.mdx b/packages/web/src/content/docs/ko/zen.mdx index a16eed4265..af74b71afd 100644 --- a/packages/web/src/content/docs/ko/zen.mdx +++ b/packages/web/src/content/docs/ko/zen.mdx @@ -80,6 +80,8 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -88,7 +90,6 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다. | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode config에서 사용하는 [모델 ID](/docs/config/#models)는 `opencode/` 형식입니다. 예를 들어 GPT 5.3 Codex를 사용하려면 config에서 `opencode/gpt-5.3-codex`를 사용하면 됩니다. @@ -114,7 +115,8 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -162,7 +164,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - MiMo V2 Pro Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - MiMo V2 Omni Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. -- Qwen3.6 Plus Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Nemotron 3 Super Free는 한정된 기간 동안 OpenCode에서 제공됩니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. - Big Pickle은 한정된 기간 동안 OpenCode에서 무료로 제공되는 stealth model입니다. 팀은 이 기간에 피드백을 수집하고 모델을 개선합니다. @@ -208,7 +209,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - MiMo V2 Pro Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - MiMo V2 Omni Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. -- Qwen3.6 Plus Free: 무료 제공 기간에는 수집된 데이터가 모델 개선에 사용될 수 있습니다. - Nemotron 3 Super Free(NVIDIA 무료 엔드포인트): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)에 따라 제공됩니다. 평가판 전용이며 프로덕션 환경이나 민감한 데이터에는 사용할 수 없습니다. NVIDIA는 자사 모델과 서비스를 개선하기 위해 프롬프트와 출력을 기록합니다. 개인 정보나 기밀 데이터는 제출하지 마세요. - OpenAI APIs: 요청은 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data)에 따라 30일 동안 보관됩니다. - Anthropic APIs: 요청은 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage)에 따라 30일 동안 보관됩니다. diff --git a/packages/web/src/content/docs/nb/go.mdx b/packages/web/src/content/docs/nb/go.mdx index cdf0c1b460..1638e5e489 100644 --- a/packages/web/src/content/docs/nb/go.mdx +++ b/packages/web/src/content/docs/nb/go.mdx @@ -69,6 +69,8 @@ Den nåværende listen over modeller inkluderer: - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** Listen over modeller kan endres etter hvert som vi tester og legger til nye. @@ -87,17 +89,25 @@ Grensene er definert i dollarverdi. Dette betyr at ditt faktiske antall forespø Tabellen nedenfor gir et estimert antall forespørsler basert på typiske bruksmønstre for Go: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ------------------------ | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| forespørsler per 5 timer | 880 | 1 150 | 1 850 | 1 290 | 2 150 | 14 000 | 20 000 | -| forespørsler per uke | 2 150 | 2 880 | 4 630 | 3 225 | 5 450 | 35 000 | 50 000 | -| forespørsler per måned | 4 300 | 5 750 | 9 250 | 6 450 | 10 900 | 70 000 | 100 000 | +| Model | forespørsler per 5 timer | forespørsler per uke | forespørsler per måned | +| ------------ | ------------------------ | -------------------- | ---------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Estimatene er basert på observerte gjennomsnittlige forespørselsmønstre: - GLM-5/5.1 — 700 input, 52 000 bufret, 150 output-tokens per forespørsel - Kimi K2.5 — 870 input, 55 000 bufret, 200 output-tokens per forespørsel - MiniMax M2.7/M2.5 — 300 input, 55 000 bufret, 125 output-tokens per forespørsel +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 input, 41 000 bufret, 250 output-tokens per forespørsel - MiMo-V2-Omni — 1000 input, 60 000 bufret, 140 output-tokens per forespørsel @@ -132,6 +142,8 @@ Du kan også få tilgang til Go-modeller gjennom følgende API-endepunkter. | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [Modell-ID-en](/docs/config/#models) i din OpenCode-konfigurasjon bruker formatet `opencode-go/`. For eksempel, for Kimi K2.5, vil du diff --git a/packages/web/src/content/docs/nb/zen.mdx b/packages/web/src/content/docs/nb/zen.mdx index e89c9ef313..a216be1064 100644 --- a/packages/web/src/content/docs/nb/zen.mdx +++ b/packages/web/src/content/docs/nb/zen.mdx @@ -89,6 +89,8 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -97,7 +99,6 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter. | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [modell-id](/docs/config/#models) i OpenCode-konfigurasjonen din @@ -125,7 +126,8 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**. | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -173,7 +175,6 @@ Gratis-modellene: - MiniMax M2.5 Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - MiMo V2 Pro Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - MiMo V2 Omni Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. -- Qwen3.6 Plus Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Nemotron 3 Super Free er tilgjengelig på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. - Big Pickle er en stealth-modell som er gratis på OpenCode i en begrenset periode. Teamet bruker denne tiden til å samle inn tilbakemeldinger og forbedre modellen. @@ -222,7 +223,6 @@ Alle modellene våre hostes i US. Leverandørene våre følger en policy for zer - MiniMax M2.5 Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - MiMo V2 Pro Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - MiMo V2 Omni Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. -- Qwen3.6 Plus Free: I gratisperioden kan innsamlede data brukes til å forbedre modellen. - Nemotron 3 Super Free (gratis NVIDIA-endepunkter): Leveres under [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Kun for prøvebruk, ikke for produksjon eller sensitive data. Prompter og svar logges av NVIDIA for å forbedre modellene og tjenestene deres. Ikke send inn personopplysninger eller konfidensielle data. - OpenAI APIs: Forespørsler lagres i 30 dager i samsvar med [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Forespørsler lagres i 30 dager i samsvar med [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/pl/go.mdx b/packages/web/src/content/docs/pl/go.mdx index a11a38d3ef..c5f17672ae 100644 --- a/packages/web/src/content/docs/pl/go.mdx +++ b/packages/web/src/content/docs/pl/go.mdx @@ -63,6 +63,8 @@ Obecna lista modeli obejmuje: - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** Lista modeli może ulec zmianie w miarę testowania i dodawania nowych. @@ -81,17 +83,25 @@ Limity są zdefiniowane w wartości w dolarach. Oznacza to, że rzeczywista licz Poniższa tabela przedstawia szacunkową liczbę żądań na podstawie typowych wzorców korzystania z Go: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| żądania na 5 godzin | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| żądania na tydzień | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| żądania na miesiąc | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | żądania na 5 godzin | żądania na tydzień | żądania na miesiąc | +| ------------ | ------------------- | ------------------ | ------------------ | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Szacunki opierają się na zaobserwowanych średnich wzorcach żądań: - GLM-5/5.1 — 700 tokenów wejściowych, 52 000 w pamięci podręcznej, 150 tokenów wyjściowych na żądanie - Kimi K2.5 — 870 tokenów wejściowych, 55 000 w pamięci podręcznej, 200 tokenów wyjściowych na żądanie - MiniMax M2.7/M2.5 — 300 tokenów wejściowych, 55 000 w pamięci podręcznej, 125 tokenów wyjściowych na żądanie +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 tokenów wejściowych, 41 000 w pamięci podręcznej, 250 tokenów wyjściowych na żądanie - MiMo-V2-Omni — 1000 tokenów wejściowych, 60 000 w pamięci podręcznej, 140 tokenów wyjściowych na żądanie @@ -124,6 +134,8 @@ Możesz również uzyskać dostęp do modeli Go za pośrednictwem następującyc | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [ID modelu](/docs/config/#models) w Twojej konfiguracji OpenCode używa formatu `opencode-go/`. Na przykład dla Kimi K2.5 należy użyć diff --git a/packages/web/src/content/docs/pl/zen.mdx b/packages/web/src/content/docs/pl/zen.mdx index 2687ec6089..ffbdb66fa9 100644 --- a/packages/web/src/content/docs/pl/zen.mdx +++ b/packages/web/src/content/docs/pl/zen.mdx @@ -89,6 +89,8 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -97,7 +99,6 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API. | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [ID modelu](/docs/config/#models) w Twojej konfiguracji OpenCode używa formatu @@ -125,7 +126,8 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów* | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -174,7 +176,6 @@ Darmowe modele: - MiniMax M2.5 Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - MiMo V2 Pro Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - MiMo V2 Omni Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. -- Qwen3.6 Plus Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Nemotron 3 Super Free jest dostępny w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. - Big Pickle to stealth model, który jest darmowy w OpenCode przez ograniczony czas. Zespół wykorzystuje ten czas do zbierania opinii i ulepszania modelu. @@ -223,7 +224,6 @@ Wszystkie nasze modele są hostowane w US. Nasi dostawcy stosują politykę zero - MiniMax M2.5 Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - MiMo V2 Pro Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - MiMo V2 Omni Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. -- Qwen3.6 Plus Free: W czasie darmowego okresu zebrane dane mogą być wykorzystywane do ulepszania modelu. - Nemotron 3 Super Free (darmowe endpointy NVIDIA): Udostępniany zgodnie z [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Tylko do użytku próbnego, nie do produkcji ani danych wrażliwych. NVIDIA rejestruje prompty i odpowiedzi, aby ulepszać swoje modele i usługi. Nie przesyłaj danych osobowych ani poufnych. - OpenAI APIs: Żądania są przechowywane przez 30 dni zgodnie z [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Żądania są przechowywane przez 30 dni zgodnie z [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/pt-br/go.mdx b/packages/web/src/content/docs/pt-br/go.mdx index ef1827b639..48afe36d90 100644 --- a/packages/web/src/content/docs/pt-br/go.mdx +++ b/packages/web/src/content/docs/pt-br/go.mdx @@ -69,6 +69,8 @@ A lista atual de modelos inclui: - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** A lista de modelos pode mudar conforme testamos e adicionamos novos. @@ -87,17 +89,25 @@ Os limites são definidos em valor em dólares. Isso significa que a sua contage A tabela abaixo fornece uma contagem estimada de requisições com base nos padrões típicos de uso do Go: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ----------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| requisições por 5 horas | 880 | 1.150 | 1.850 | 1.290 | 2.150 | 14.000 | 20.000 | -| requisições por semana | 2.150 | 2.880 | 4.630 | 3.225 | 5.450 | 35.000 | 50.000 | -| requisições por mês | 4.300 | 5.750 | 9.250 | 6.450 | 10.900 | 70.000 | 100.000 | +| Model | requisições por 5 horas | requisições por semana | requisições por mês | +| ------------ | ----------------------- | ---------------------- | ------------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | As estimativas baseiam-se nos padrões médios de requisições observados: - GLM-5/5.1 — 700 tokens de entrada, 52.000 em cache, 150 tokens de saída por requisição - Kimi K2.5 — 870 tokens de entrada, 55.000 em cache, 200 tokens de saída por requisição - MiniMax M2.7/M2.5 — 300 tokens de entrada, 55.000 em cache, 125 tokens de saída por requisição +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 tokens de entrada, 41.000 em cache, 250 tokens de saída por requisição - MiMo-V2-Omni — 1000 tokens de entrada, 60.000 em cache, 140 tokens de saída por requisição @@ -132,6 +142,8 @@ Você também pode acessar os modelos do Go através dos seguintes endpoints de | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | O [ID do modelo](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode-go/`. Por exemplo, para o Kimi K2.5, você usaria diff --git a/packages/web/src/content/docs/pt-br/zen.mdx b/packages/web/src/content/docs/pt-br/zen.mdx index 6cbc810da7..d911e441f4 100644 --- a/packages/web/src/content/docs/pt-br/zen.mdx +++ b/packages/web/src/content/docs/pt-br/zen.mdx @@ -80,6 +80,8 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -88,7 +90,6 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API. | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | O [model id](/docs/config/#models) na sua configuração do OpenCode usa o formato `opencode/`. Por exemplo, para GPT 5.3 Codex, você usaria `opencode/gpt-5.3-codex` na sua configuração. @@ -114,7 +115,8 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**. | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -162,7 +164,6 @@ Os modelos gratuitos: - MiniMax M2.5 Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - MiMo V2 Pro Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - MiMo V2 Omni Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. -- Qwen3.6 Plus Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Nemotron 3 Super Free está disponível no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. - Big Pickle é um modelo stealth que está gratuito no OpenCode por tempo limitado. A equipe está usando esse período para coletar feedback e melhorar o modelo. @@ -208,7 +209,6 @@ Todos os nossos modelos são hospedados nos US. Nossos provedores seguem uma pol - MiniMax M2.5 Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - MiMo V2 Pro Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - MiMo V2 Omni Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. -- Qwen3.6 Plus Free: Durante seu período gratuito, os dados coletados podem ser usados para melhorar o modelo. - Nemotron 3 Super Free (endpoints gratuitos da NVIDIA): Fornecido sob os [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Apenas para uso de avaliação, não para produção nem dados sensíveis. A NVIDIA registra prompts e saídas para melhorar seus modelos e serviços. Não envie dados pessoais ou confidenciais. - OpenAI APIs: As solicitações são retidas por 30 dias de acordo com [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: As solicitações são retidas por 30 dias de acordo com [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/ru/go.mdx b/packages/web/src/content/docs/ru/go.mdx index ed74aed731..dacaf65273 100644 --- a/packages/web/src/content/docs/ru/go.mdx +++ b/packages/web/src/content/docs/ru/go.mdx @@ -69,6 +69,8 @@ OpenCode Go работает так же, как и любой другой пр - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** Список моделей может меняться по мере того, как мы тестируем и добавляем новые. @@ -87,17 +89,25 @@ OpenCode Go включает следующие лимиты: В таблице ниже приведено примерное количество запросов на основе типичных сценариев использования Go: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| запросов за 5 часов | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| запросов в неделю | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| запросов в месяц | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | запросов за 5 часов | запросов в неделю | запросов в месяц | +| ------------ | ------------------- | ----------------- | ---------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Оценки основаны на наблюдаемых средних показателях запросов: - GLM-5/5.1 — 700 входных, 52,000 кешированных, 150 выходных токенов на запрос - Kimi K2.5 — 870 входных, 55,000 кешированных, 200 выходных токенов на запрос - MiniMax M2.7/M2.5 — 300 входных, 55,000 кешированных, 125 выходных токенов на запрос +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 входных, 41,000 кешированных, 250 выходных токенов на запрос - MiMo-V2-Omni — 1000 входных, 60,000 кешированных, 140 выходных токенов на запрос @@ -132,6 +142,8 @@ OpenCode Go включает следующие лимиты: | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [ID модели](/docs/config/#models) в вашем конфиге OpenCode использует формат `opencode-go/`. Например, для Kimi K2.5 вам нужно diff --git a/packages/web/src/content/docs/ru/zen.mdx b/packages/web/src/content/docs/ru/zen.mdx index 555cda1f36..8a92bf502e 100644 --- a/packages/web/src/content/docs/ru/zen.mdx +++ b/packages/web/src/content/docs/ru/zen.mdx @@ -89,6 +89,8 @@ OpenCode Zen работает как любой другой провайдер | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -97,7 +99,6 @@ OpenCode Zen работает как любой другой провайдер | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [идентификатор модели](/docs/config/#models) в вашей конфигурации OpenCode @@ -125,7 +126,8 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -173,7 +175,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - MiMo V2 Pro Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - MiMo V2 Omni Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. -- Qwen3.6 Plus Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Nemotron 3 Super Free доступна в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. - Big Pickle — это скрытая модель, которая доступна бесплатно в OpenCode ограниченное время. Команда использует это время, чтобы собирать отзывы и улучшать модель. @@ -222,7 +223,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - MiMo V2 Pro Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - MiMo V2 Omni Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. -- Qwen3.6 Plus Free: во время бесплатного периода собранные данные могут использоваться для улучшения модели. - Nemotron 3 Super Free (бесплатные эндпоинты NVIDIA): предоставляется в соответствии с [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Только для пробного использования, не для продакшена и не для чувствительных данных. NVIDIA логирует запросы и ответы, чтобы улучшать свои модели и сервисы. Не отправляйте персональные или конфиденциальные данные. - OpenAI APIs: запросы хранятся 30 дней в соответствии с [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: запросы хранятся 30 дней в соответствии с [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/th/go.mdx b/packages/web/src/content/docs/th/go.mdx index b0cf2a8bf8..aa26f7dcf1 100644 --- a/packages/web/src/content/docs/th/go.mdx +++ b/packages/web/src/content/docs/th/go.mdx @@ -59,6 +59,8 @@ OpenCode Go ทำงานเหมือนกับผู้ให้บร - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** รายชื่อโมเดลอาจมีการเปลี่ยนแปลงเมื่อเราทำการทดสอบและเพิ่มโมเดลใหม่ๆ @@ -77,17 +79,25 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: ตารางด้านล่างแสดงจำนวน request โดยประมาณตามรูปแบบการใช้งานปกติของ Go: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ---------------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| requests ต่อ 5 ชั่วโมง | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| requests ต่อสัปดาห์ | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| requests ต่อเดือน | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | requests ต่อ 5 ชั่วโมง | requests ต่อสัปดาห์ | requests ต่อเดือน | +| ------------ | ---------------------- | ------------------- | ----------------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | การประมาณการอ้างอิงจากรูปแบบการใช้งาน request โดยเฉลี่ยที่สังเกตพบ: - GLM-5/5.1 — 700 input, 52,000 cached, 150 output tokens ต่อ request - Kimi K2.5 — 870 input, 55,000 cached, 200 output tokens ต่อ request - MiniMax M2.7/M2.5 — 300 input, 55,000 cached, 125 output tokens ต่อ request +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 350 input, 41,000 cached, 250 output tokens ต่อ request - MiMo-V2-Omni — 1000 input, 60,000 cached, 140 output tokens ต่อ request @@ -120,6 +130,8 @@ OpenCode Go มีขีดจำกัดดังต่อไปนี้: | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | [model id](/docs/config/#models) ใน OpenCode config ของคุณจะใช้รูปแบบ `opencode-go/` ตัวอย่างเช่น สำหรับ Kimi K2.5 คุณจะใช้ `opencode-go/kimi-k2.5` ใน config ของคุณ diff --git a/packages/web/src/content/docs/th/zen.mdx b/packages/web/src/content/docs/th/zen.mdx index 527bcf7db4..2c82c1e07d 100644 --- a/packages/web/src/content/docs/th/zen.mdx +++ b/packages/web/src/content/docs/th/zen.mdx @@ -82,6 +82,8 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -90,7 +92,6 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | [model id](/docs/config/#models) ใน OpenCode config ของคุณใช้รูปแบบ `opencode/` ตัวอย่างเช่น สำหรับ GPT 5.3 Codex คุณจะใช้ `opencode/gpt-5.3-codex` ใน config ของคุณ @@ -116,7 +117,8 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -164,7 +166,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - MiMo V2 Pro Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - MiMo V2 Omni Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล -- Qwen3.6 Plus Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Nemotron 3 Super Free เปิดให้ใช้บน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล - Big Pickle เป็น stealth model ที่ใช้งานฟรีบน OpenCode ในช่วงเวลาจำกัด ทีมกำลังใช้ช่วงเวลานี้เพื่อเก็บ feedback และปรับปรุงโมเดล @@ -210,7 +211,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - MiMo V2 Pro Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - MiMo V2 Omni Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล -- Qwen3.6 Plus Free: ระหว่างช่วงที่เปิดให้ใช้ฟรี ข้อมูลที่เก็บรวบรวมอาจถูกนำไปใช้เพื่อปรับปรุงโมเดล - Nemotron 3 Super Free (endpoint ฟรีของ NVIDIA): ให้บริการภายใต้ [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) ใช้สำหรับการทดลองเท่านั้น ไม่เหมาะสำหรับ production หรือข้อมูลที่อ่อนไหว NVIDIA จะบันทึก prompt และ output เพื่อนำไปปรับปรุงโมเดลและบริการของตน โปรดอย่าส่งข้อมูลส่วนบุคคลหรือข้อมูลลับ. - OpenAI APIs: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: คำขอจะถูกเก็บไว้เป็นเวลา 30 วันตาม [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/tr/go.mdx b/packages/web/src/content/docs/tr/go.mdx index b7b24b8dec..b085f6d060 100644 --- a/packages/web/src/content/docs/tr/go.mdx +++ b/packages/web/src/content/docs/tr/go.mdx @@ -59,6 +59,8 @@ Mevcut model listesi şunları içerir: - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** Test edip yenilerini ekledikçe model listesi değişebilir. @@ -77,17 +79,25 @@ Limitler dolar değeri üzerinden belirlenmiştir. Bu, gerçek istek sayınızı Aşağıdaki tablo, tipik Go kullanım modellerine dayalı tahmini bir istek sayısı sunmaktadır: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| ------------------ | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| 5 saatte bir istek | 880 | 1.150 | 1.850 | 1.290 | 2.150 | 14.000 | 20.000 | -| haftalık istek | 2.150 | 2.880 | 4.630 | 3.225 | 5.450 | 35.000 | 50.000 | -| aylık istek | 4.300 | 5.750 | 9.250 | 6.450 | 10.900 | 70.000 | 100.000 | +| Model | 5 saatte bir istek | haftalık istek | aylık istek | +| ------------ | ------------------ | -------------- | ----------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | Tahminler, gözlemlenen ortalama istek modellerine dayanmaktadır: - GLM-5/5.1 — İstek başına 700 girdi, 52.000 önbelleğe alınmış, 150 çıktı token'ı - Kimi K2.5 — İstek başına 870 girdi, 55.000 önbelleğe alınmış, 200 çıktı token'ı - MiniMax M2.7/M2.5 — İstek başına 300 girdi, 55.000 önbelleğe alınmış, 125 çıktı token'ı +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — İstek başına 350 girdi, 41.000 önbelleğe alınmış, 250 çıktı token'ı - MiMo-V2-Omni — İstek başına 1000 girdi, 60.000 önbelleğe alınmış, 140 çıktı token'ı @@ -120,6 +130,8 @@ Go modellerine aşağıdaki API uç noktaları aracılığıyla da erişebilirsi | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | OpenCode yapılandırmanızdaki [model id](/docs/config/#models) formatı `opencode-go/` şeklindedir. Örneğin, Kimi K2.5 için yapılandırmanızda `opencode-go/kimi-k2.5` kullanmalısınız. diff --git a/packages/web/src/content/docs/tr/zen.mdx b/packages/web/src/content/docs/tr/zen.mdx index 17166f84d4..30aa2bb9d7 100644 --- a/packages/web/src/content/docs/tr/zen.mdx +++ b/packages/web/src/content/docs/tr/zen.mdx @@ -80,6 +80,8 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -88,7 +90,6 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode yapılandırmanızdaki [model id](/docs/config/#models) `opencode/` biçimini kullanır. Örneğin, GPT 5.3 Codex için yapılandırmanızda `opencode/gpt-5.3-codex` kullanırsınız. @@ -114,7 +115,8 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -162,7 +164,6 @@ Kredi kartı ücretleri maliyet üzerinden yansıtılır (%4.4 + işlem başına - MiniMax M2.5 Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - MiMo V2 Pro Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - MiMo V2 Omni Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. -- Qwen3.6 Plus Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Nemotron 3 Super Free, sınırlı bir süre için OpenCode'da ücretsizdir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. - Big Pickle, sınırlı bir süre için OpenCode'da ücretsiz olan gizli bir modeldir. Ekip bu süreyi geri bildirim toplamak ve modeli iyileştirmek için kullanıyor. @@ -208,7 +209,6 @@ Tüm modellerimiz US'de barındırılıyor. Sağlayıcılarımız zero-retention - MiniMax M2.5 Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - MiMo V2 Pro Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - MiMo V2 Omni Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. -- Qwen3.6 Plus Free: Ücretsiz döneminde toplanan veriler modeli iyileştirmek için kullanılabilir. - Nemotron 3 Super Free (ücretsiz NVIDIA uç noktaları): [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) kapsamında sunulur. Yalnızca deneme amaçlıdır; üretim veya hassas veriler için uygun değildir. NVIDIA, modellerini ve hizmetlerini geliştirmek için promptları ve çıktıları kaydeder. Kişisel veya gizli veri göndermeyin. - OpenAI APIs: İstekler [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) uyarınca 30 gün boyunca saklanır. - Anthropic APIs: İstekler [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) uyarınca 30 gün boyunca saklanır. diff --git a/packages/web/src/content/docs/zen.mdx b/packages/web/src/content/docs/zen.mdx index cd574a30b7..cf634a9c98 100644 --- a/packages/web/src/content/docs/zen.mdx +++ b/packages/web/src/content/docs/zen.mdx @@ -89,13 +89,14 @@ You can also access our models through the following API endpoints. | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5 | glm-5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | The [model id](/docs/config/#models) in your OpenCode config @@ -121,14 +122,14 @@ 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 | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | | GLM 5.1 | $1.40 | $4.40 | $0.26 | - | | GLM 5 | $1.00 | $3.20 | $0.20 | - | | Kimi K2.5 | $0.60 | $3.00 | $0.10 | - | -| Qwen3 Coder 480B | $0.45 | $1.50 | - | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 | | Claude Opus 4.1 | $15.00 | $75.00 | $1.50 | $18.75 | @@ -167,7 +168,6 @@ Credit card fees are passed along at cost (4.4% + $0.30 per transaction); we don The free models: - MiniMax M2.5 Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. -- Qwen3.6 Plus Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Nemotron 3 Super Free is available on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. - Big Pickle is a stealth model that's free on OpenCode for a limited time. The team is using this time to collect feedback and improve the model. @@ -214,7 +214,6 @@ All our models are hosted in the US. Our providers follow a zero-retention polic - Big Pickle: During its free period, collected data may be used to improve the model. - MiniMax M2.5 Free: During its free period, collected data may be used to improve the model. -- Qwen3.6 Plus Free: During its free period, collected data may be used to improve the model. - Nemotron 3 Super Free (NVIDIA free endpoints): Provided under the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf). Trial use only — not for production or sensitive data. Prompts and outputs are logged by NVIDIA to improve its models and services. Do not submit personal or confidential data. - OpenAI APIs: Requests are retained for 30 days in accordance with [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data). - Anthropic APIs: Requests are retained for 30 days in accordance with [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage). diff --git a/packages/web/src/content/docs/zh-cn/go.mdx b/packages/web/src/content/docs/zh-cn/go.mdx index 7b1a3b77fc..df74f35ec1 100644 --- a/packages/web/src/content/docs/zh-cn/go.mdx +++ b/packages/web/src/content/docs/zh-cn/go.mdx @@ -59,6 +59,8 @@ OpenCode Go 的工作方式与 OpenCode 中的其他提供商一样。 - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** 随着我们进行测试和添加新模型,该列表可能会发生变化。 @@ -77,11 +79,17 @@ OpenCode Go 包含以下限制: 下表提供了基于典型 Go 使用模式的预估请求数: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| --------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| 每 5 小时请求数 | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| 每周请求数 | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| 每月请求数 | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | 每 5 小时请求数 | 每周请求数 | 每月请求数 | +| ------------ | --------------- | ---------- | ---------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | 预估值基于观察到的平均请求模式: @@ -90,6 +98,8 @@ OpenCode Go 包含以下限制: - MiMo-V2-Pro — 每次请求 350 个输入 token,41,000 个缓存 token,250 个输出 token - MiMo-V2-Omni — 每次请求 1000 个输入 token,60,000 个缓存 token,140 个输出 token - MiniMax M2.7/M2.5 — 每次请求 300 个输入 token,55,000 个缓存 token,125 个输出 token +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request 你可以在 **控制台** 中跟踪你当前的使用情况。 @@ -120,6 +130,8 @@ OpenCode Go 包含以下限制: | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | 你 OpenCode 配置中的 [模型 ID](/docs/config/#models) 使用 `opencode-go/` 格式。例如,对于 Kimi K2.5,你将在配置中使用 `opencode-go/kimi-k2.5`。 diff --git a/packages/web/src/content/docs/zh-cn/zen.mdx b/packages/web/src/content/docs/zh-cn/zen.mdx index f17a0f842b..cd281a4b0c 100644 --- a/packages/web/src/content/docs/zh-cn/zen.mdx +++ b/packages/web/src/content/docs/zh-cn/zen.mdx @@ -80,6 +80,8 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -88,7 +90,6 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。 | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | 在你的 OpenCode 配置中,[模型 ID](/docs/config/#models) 使用 `opencode/` 格式。例如,对于 GPT 5.3 Codex,你需要在配置中使用 `opencode/gpt-5.3-codex`。 @@ -114,7 +115,8 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -162,7 +164,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - MiMo V2 Pro Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - MiMo V2 Omni Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 -- Qwen3.6 Plus Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Nemotron 3 Super Free 目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 - Big Pickle 是一个隐身模型,目前在 OpenCode 上限时免费提供。团队正在利用这段时间收集反馈并改进模型。 @@ -208,7 +209,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free:在免费期间,收集的数据可能会被用于改进模型。 - MiMo V2 Pro Free:在免费期间,收集的数据可能会被用于改进模型。 - MiMo V2 Omni Free:在免费期间,收集的数据可能会被用于改进模型。 -- Qwen3.6 Plus Free:在免费期间,收集的数据可能会被用于改进模型。 - Nemotron 3 Super Free(NVIDIA 免费端点):根据 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。仅供试用,不适用于生产环境或敏感数据。NVIDIA 会记录提示词和输出内容,以改进其模型和服务。请勿提交个人或机密数据。 - OpenAI APIs:请求会根据 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。 - Anthropic APIs:请求会根据 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 保留 30 天。 diff --git a/packages/web/src/content/docs/zh-tw/go.mdx b/packages/web/src/content/docs/zh-tw/go.mdx index b6b76afffc..9c12710a07 100644 --- a/packages/web/src/content/docs/zh-tw/go.mdx +++ b/packages/web/src/content/docs/zh-tw/go.mdx @@ -59,6 +59,8 @@ OpenCode Go 的運作方式與 OpenCode 中的任何其他供應商相同。 - **MiMo-V2-Pro** - **MiMo-V2-Omni** - **MiniMax M2.5** +- **Qwen3.5 Plus** +- **Qwen3.6 Plus** - **MiniMax M2.7** 隨著我們測試並加入新模型,模型清單可能會有所變動。 @@ -77,17 +79,25 @@ OpenCode Go 包含以下限制: 下表提供了基於典型 Go 使用模式的預估請求次數: -| | GLM-5.1 | GLM-5 | Kimi K2.5 | MiMo-V2-Pro | MiMo-V2-Omni | MiniMax M2.7 | MiniMax M2.5 | -| --------------- | ------- | ----- | --------- | ----------- | ------------ | ------------ | ------------ | -| 每 5 小時請求數 | 880 | 1,150 | 1,850 | 1,290 | 2,150 | 14,000 | 20,000 | -| 每週請求數 | 2,150 | 2,880 | 4,630 | 3,225 | 5,450 | 35,000 | 50,000 | -| 每月請求數 | 4,300 | 5,750 | 9,250 | 6,450 | 10,900 | 70,000 | 100,000 | +| Model | 每 5 小時請求數 | 每週請求數 | 每月請求數 | +| ------------ | --------------- | ---------- | ---------- | +| GLM-5.1 | 880 | 2,150 | 4,300 | +| GLM-5 | 1,150 | 2,880 | 5,750 | +| Kimi K2.5 | 1,850 | 4,630 | 9,250 | +| MiMo-V2-Pro | 1,290 | 3,225 | 6,450 | +| MiMo-V2-Omni | 2,150 | 5,450 | 10,900 | +| Qwen3.6 Plus | 3,300 | 8,200 | 16,300 | +| MiniMax M2.7 | 3,400 | 8,500 | 17,000 | +| MiniMax M2.5 | 6,300 | 15,900 | 31,800 | +| Qwen3.5 Plus | 10,200 | 25,200 | 50,500 | 預估值是基於觀察到的平均請求模式: - GLM-5/5.1 — 每次請求 700 個輸入 token、52,000 個快取 token、150 個輸出 token - Kimi K2.5 — 每次請求 870 個輸入 token、55,000 個快取 token、200 個輸出 token - MiniMax M2.7/M2.5 — 每次請求 300 個輸入 token、55,000 個快取 token、125 個輸出 token +- Qwen3.5 Plus — 410 input, 47,000 cached, 140 output tokens per request +- Qwen3.6 Plus — 500 input, 57,000 cached, 190 output tokens per request - MiMo-V2-Pro — 每次請求 350 個輸入 token、41,000 個快取 token、250 個輸出 token - MiMo-V2-Omni — 每次請求 1000 個輸入 token、60,000 個快取 token、140 個輸出 token @@ -120,6 +130,8 @@ OpenCode Go 包含以下限制: | MiMo-V2-Omni | mimo-v2-omni | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.7 | minimax-m2.7 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/alibaba` | 您的 OpenCode 設定中的 [model id](/docs/config/#models) 使用 `opencode-go/` 格式。例如,Kimi K2.5 在設定中應使用 `opencode-go/kimi-k2.5`。 diff --git a/packages/web/src/content/docs/zh-tw/zen.mdx b/packages/web/src/content/docs/zh-tw/zen.mdx index 3a6448b593..bdc3c07db6 100644 --- a/packages/web/src/content/docs/zh-tw/zen.mdx +++ b/packages/web/src/content/docs/zh-tw/zen.mdx @@ -84,6 +84,8 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` | | Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` | | Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` | +| Qwen3.6 Plus | qwen3.6-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | +| Qwen3.5 Plus | qwen3.5-plus | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/alibaba` | | MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | GLM 5.1 | glm-5.1 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | @@ -92,7 +94,6 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。 | Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Pro Free | mimo-v2-pro-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | MiMo V2 Omni Free | mimo-v2-omni-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | -| Qwen3.6 Plus Free | qwen3.6-plus-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | | Nemotron 3 Super Free | nemotron-3-super-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` | OpenCode 設定中的 [模型 ID](/docs/config/#models) 會使用 `opencode/` @@ -119,7 +120,8 @@ https://opencode.ai/zen/v1/models | Big Pickle | Free | Free | Free | - | | MiMo V2 Pro Free | Free | Free | Free | - | | MiMo V2 Omni Free | Free | Free | Free | - | -| Qwen3.6 Plus Free | Free | Free | Free | - | +| Qwen3.6 Plus | $0.50 | $3.00 | $0.05 | $0.625 | +| Qwen3.5 Plus | $0.20 | $1.20 | $0.02 | $0.25 | | Nemotron 3 Super Free | Free | Free | Free | - | | MiniMax M2.5 Free | Free | Free | Free | - | | MiniMax M2.5 | $0.30 | $1.20 | $0.06 | $0.375 | @@ -168,7 +170,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - MiMo V2 Pro Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - MiMo V2 Omni Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 -- Qwen3.6 Plus Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Nemotron 3 Super Free 在 OpenCode 上限時提供。團隊正在利用這段時間收集回饋並改進模型。 - Big Pickle 是一個隱身模型,在 OpenCode 上限時免費提供。團隊正在利用這段時間收集回饋並改進模型。 @@ -215,7 +216,6 @@ https://opencode.ai/zen/v1/models - MiniMax M2.5 Free: 在免費期間,收集到的資料可能會用於改進模型。 - MiMo V2 Pro Free: 在免費期間,收集到的資料可能會用於改進模型。 - MiMo V2 Omni Free: 在免費期間,收集到的資料可能會用於改進模型。 -- Qwen3.6 Plus Free: 在免費期間,收集到的資料可能會用於改進模型。 - Nemotron 3 Super Free(NVIDIA 免費端點):依據 [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) 提供。僅供試用,不適用於正式環境或敏感資料。NVIDIA 會記錄提示詞與輸出內容,以改進其模型與服務。請勿提交個人或機密資料。 - OpenAI APIs: 請求會依據 [OpenAI's Data Policies](https://platform.openai.com/docs/guides/your-data) 保留 30 天。 - Anthropic APIs: 請求會依據 [Anthropic's Data Policies](https://docs.anthropic.com/en/docs/claude-code/data-usage) 保留 30 天。 diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index e0b583a78b..634583bd31 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.4.4", + "version": "1.4.6", "publisher": "sst-dev", "repository": { "type": "git",