Compare commits

..

97 Commits

Author SHA1 Message Date
Filip Hejmowski 4fdb104bb5 feat: discover models 2026-08-17 18:23:50 +02:00
Filip Hejmowski b02419621b Merge remote-tracking branch 'origin/azure-cli-auth' into azure-cli-auth 2026-08-17 17:54:29 +02:00
Filip Hejmowski 95f098a721 remove overengineering 2026-08-17 17:52:14 +02:00
Filip Hejmowski 8c39e13b77 wip 2026-08-17 17:09:59 +02:00
Filip ed11c895c6 Merge branch 'dev' into azure-cli-auth 2026-08-17 14:48:01 +02:00
Filip Hejmowski 8bc87d3c6a better listing 2026-08-17 14:33:37 +02:00
Filip Hejmowski 1ec78c705a clean uppp 2026-08-17 13:41:54 +02:00
opencode-agent[bot] 2cba7e227d fix(cli): update default console URL (#43043)
Co-authored-by: Victor Navarro <36263538+vimtor@users.noreply.github.com>
2026-08-17 13:31:33 +02:00
Filip Hejmowski ede6c4f1a6 remove unnecessary maunal resource / sub id handling 2026-08-17 13:10:51 +02:00
Filip Hejmowski c34171845e refactor 2026-08-17 12:50:57 +02:00
Filip Hejmowski 4a7a497597 proper model filtering for azure 2026-08-17 12:38:41 +02:00
Filip Hejmowski b7f72b1bd4 feat: azure oauth 2026-08-17 12:09:11 +02:00
Game On 4d68d30b48 tweak: match codex limits for openai models exactly when using chatgpt subscription (#39082) 2026-08-17 00:39:52 -05:00
Aiden Cline cff8ba313a Merge branch 'dev' into feat/azure-oauth 2026-08-17 00:01:12 -05:00
opencode-agent[bot] 5a0e07efcf chore: generate 2026-08-17 04:54:34 +00:00
Aiden Cline cba6b5f2f7 feat(opencode): native OpenAI and Anthropic passthroughs for Cloudflare AI Gateway (#42634)
Co-authored-by: Keefe Tang <keefe@pentagram.me>
2026-08-16 23:53:20 -05:00
opencode-agent[bot] 1c965451b5 fix(stats): correct YouTube footer link (#42941)
Co-authored-by: Filip <34747899+neriousy@users.noreply.github.com>
2026-08-16 23:58:45 +02:00
Jack a0f8dccbfe docs: update DeepSeek V4 pricing (#42881) 2026-08-17 00:01:15 +08:00
Dax Raad fb8344f3c2 chore: remove scheduled beta sync 2026-08-16 11:26:41 -04:00
Frank 3fd77ae980 zen: peak pricing 2026-08-16 05:20:04 -04:00
Jack 976c185172 docs(go): remove DeepSeek Flash promotion (#42858) 2026-08-16 14:08:47 +08:00
opencode-agent[bot] 4643e65ad6 fix(opencode): enable web search for Go (#42630)
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
2026-08-14 13:23:26 -05:00
Jack e23586af26 feat(go): add GLM 5.3 (#42518) 2026-08-14 13:48:32 +08:00
opencode-agent[bot] 92d29ba4a3 chore: generate 2026-08-14 04:17:00 +00:00
Jack 886fd98f52 docs(zen): add Muse Spark 1.2 (#42508) 2026-08-14 12:15:43 +08:00
opencode-agent[bot] 722e717e99 chore: update nix node_modules hashes 2026-08-14 03:25:42 +00:00
Aiden Cline 6d635007ab chore(deps): update ai-gateway-provider to 3.2.0 (#42488)
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
2026-08-13 22:08:45 -05:00
opencode-agent[bot] 0e3474509a docs: sort Gemini 3.7 before 3.6 (#42473)
Co-authored-by: Stefan Avram <98915060+Slickstef11@users.noreply.github.com>
2026-08-14 10:24:22 +08:00
Dax d8bf79225f fix(opencode): preserve v1 database compatibility (#42444) 2026-08-14 00:05:15 +00:00
opencode-agent[bot] 8a55ba75b5 chore: generate 2026-08-13 18:57:31 +00:00
Jack 2449581543 fix(go): remove Gemini 3.7 Flash (#42393) 2026-08-14 02:55:24 +08:00
opencode-agent[bot] 3e25e80f7a chore: generate 2026-08-13 18:44:28 +00:00
Jack f06e9491e1 feat(go): add Gemini 3.7 Flash (#42390) 2026-08-14 02:41:12 +08:00
Jack d0c2b41adf docs(go): use responses API for Grok 4.5 (#42373) 2026-08-14 01:28:55 +08:00
Frank c7af47f9ed update grok endpoint 2026-08-13 13:27:41 -04:00
Kit Langton 6c035e1fd7 fix(core): preserve unicode in grep previews (#42356) 2026-08-13 16:51:43 +00:00
opencode-agent[bot] ab7cbc808f chore: generate 2026-08-13 16:30:12 +00:00
Aditya Sethi 62387f39d4 fix(skills): Update global config path in documentation (#42337) 2026-08-13 18:27:29 +02:00
Jack 864889ab9f docs: remove Ling 3.0 Tiny free model (#42314) 2026-08-13 20:48:54 +08:00
opencode cc4b456129 sync release versions for v1.18.18 2026-08-13 01:15:01 +00:00
opencode-agent[bot] 14b37df391 chore: update nix node_modules hashes 2026-08-12 22:36:40 +00:00
opencode-agent[bot] 91df883231 fix(opencode): select Kimi prompt by provider (#42161)
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
2026-08-12 17:29:03 -05:00
opencode-agent[bot] 6fea419feb fix(groq): pass through reasoning effort (#42166)
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
2026-08-12 17:20:23 -05:00
opencode-agent[bot] beeabe2e4b fix(mistral): pass through reasoning effort (#42164)
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
2026-08-12 17:20:07 -05:00
opencode-agent[bot] 502310f4df fix(xai): pass through reasoning effort (#42160)
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
2026-08-12 17:19:17 -05:00
opencode 37fe5c83dc sync release versions for v1.18.17 2026-08-12 20:25:04 +00:00
Aiden Cline dab2637217 fix(compaction): adjust instructions and structure to be more clear to smaller models like dsv4 flash (#42045)
Co-authored-by: akenra <37288280+akenra@users.noreply.github.com>
2026-08-12 14:36:51 -05:00
opencode-agent[bot] 39fb919a05 chore: add neriousy to team members (#42107)
Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
2026-08-12 13:30:38 -05:00
opencode-agent[bot] 999be62662 chore: generate 2026-08-12 16:56:04 +00:00
opencode-agent[bot] 521906f5fa docs(go): clarify DeepSeek ZDR coverage (#42085)
Co-authored-by: Dax Raad <mail@thdxr.com>
2026-08-12 12:53:58 -04:00
Frank 069165eb5b Merge branch 'dev' of github.com:anomalyco/opencode into dev 2026-08-12 12:48:10 -04:00
Frank df09c3ec61 update ds v4 pro 2026-08-12 12:48:08 -04:00
opencode-agent[bot] 6d3ae4d63d chore: generate 2026-08-12 16:47:08 +00:00
Adam 284187ac55 fix(ci): authenticate pulumi downloads 2026-08-12 11:45:00 -05:00
Adam 7e0353cca9 fix(stats): correct r2 daily totals 2026-08-12 11:36:23 -05:00
SKY ZHAO 959c8bd498 docs: fix provider display name and PAT typos (#42034)
Co-authored-by: skyzhao1223 <skyzhao1223@users.noreply.github.com>
2026-08-12 10:32:23 -05:00
SKY ZHAO ca3df21b7f docs: fix broken DigitalOcean and Daytona links (#42048)
Co-authored-by: skyzhao1223 <skyzhao1223@users.noreply.github.com>
2026-08-12 10:31:47 -05:00
Matthew Feroz 8571a922db fix(provider): add Merge Gateway reasoning variants (#41867) 2026-08-12 10:31:19 -05:00
Frank d92d1e654b docs(zen): add Grok 4.6 2026-08-12 10:53:38 -04:00
Adam 46a14e685a feat(stats): query r2 data catalog 2026-08-12 09:49:10 -05:00
Jack 1f94d8a3c8 docs(zen): remove expired free models (#41943) 2026-08-12 12:03:45 +08:00
Aiden Cline c78986831c fix(opencode): cap session retries with jitter (#41939) 2026-08-11 22:50:52 -05:00
vimtor d470434746 refactor(console): simplify go usage response 2026-08-11 23:18:57 +02:00
Steven Ao 561afb401a fix(opencode): detect Copilot PDF input support (#41522) 2026-08-11 16:04:36 -05:00
Arif Rahman (Bolt) 2b8a5969e9 feat(console): add go usage endpoint (#16513)
Co-authored-by: vimtor <vn4varro@gmail.com>
2026-08-11 22:25:23 +02:00
Jack 36b205370d docs(zen): add Hy3 Free (#41814) 2026-08-12 02:42:06 +08:00
opencode-agent[bot] 6afef2f881 fix(console): update blocked account message (#41819)
Co-authored-by: Dax Raad <d@ironbay.co>
Co-authored-by: Dax <mail@thdxr.com>
2026-08-11 14:41:37 -04:00
opencode-agent[bot] 0d927ba03f chore: generate 2026-08-11 14:17:42 +00:00
Jack 9fdd4824d3 docs(zen): add Nemotron 3.5 Lightning (#41750) 2026-08-11 22:15:53 +08:00
opencode-agent[bot] d041eee55c chore: use DeepSeek for beta conflicts (#41669)
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
2026-08-11 04:58:15 +00:00
Luke Parker d62e50075a chore: build beta from v2 (#41627) 2026-08-11 14:33:42 +10:00
opencode-agent[bot] 5d953482ab fix(provider): scope DeepSeek V4 Flash sampling defaults (#41620)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: 侯秀凯 <9627034+hoooou@users.noreply.github.com>
2026-08-10 23:01:56 -05:00
OpeOginni 3a90639cb5 fix(ui): correct OC-2 weak icon color (#41504) 2026-08-10 17:08:45 -05:00
OpeOginni 836fcfb742 Merge branch 'dev' into feat/azure-oauth 2026-08-07 12:13:56 +02:00
OpeOginni 03d1099933 Merge branch 'dev' into feat/azure-oauth 2026-07-30 19:08:05 +02:00
OpeOginni e41e416beb chore: merge dev into azure oauth 2026-07-20 17:30:37 +02:00
OpeOginni 2d16759723 Merge branch 'dev' into feat/azure-oauth 2026-07-10 11:06:33 +02:00
OpeOginni a41ed1cdf2 Merge branch 'dev' into feat/azure-oauth 2026-07-08 08:12:52 +02:00
OpeOginni 24479e2df9 fix: run az cli command/check before storing during azure provider
connection
2026-07-08 08:06:41 +02:00
OpeOginni 365a95c91b chore: improve comment v2 & moved delete of azureOpenAICompatibleBaseURL 2026-06-29 14:03:09 +02:00
OpeOginni 49ca92621d chore: improve comment 2026-06-29 14:02:35 +02:00
OpeOginni 92aa12a609 fix(provider): preserve cognitive model endpoints in Oauth connection 2026-06-29 13:53:15 +02:00
OpeOginni 484b662665 Merge branch 'dev' into feat/azure-oauth 2026-06-26 17:13:33 +02:00
OpeOginni e8d955a40c fix(provider): resolve azure cognitive services oauth endpoints 2026-06-26 17:13:03 +02:00
OpeOginni 7adfe50b9a fix(provider): resolve azure cognitive services resource vars 2026-06-23 00:14:44 +02:00
OpeOginni 5613ebb514 docs: fixed description of azure oauth 2026-06-22 17:24:03 +02:00
OpeOginni c0ecd5da0e fix: validate azure cli token expiry parsing 2026-06-22 17:17:14 +02:00
OpeOginni be5f5dbc29 fix: prefer azure cli expires_on for token cache 2026-06-22 17:13:47 +02:00
OpeOginni 3cf9cfb6f3 docs: display to use the Foundry inference role for Cognitive Services 2026-06-22 17:11:41 +02:00
OpeOginni fcc21f3dc5 fix: Remove the Anthropic API-key header when using bearer
authentication
2026-06-22 17:08:36 +02:00
OpeOginni fa40659b7f fix: Preserve model-specific endpoints for Azure Cognitive Services
OAuth
2026-06-22 17:07:38 +02:00
OpeOginni fdf14a8462 chore: improved azure oauth method name and ran format 2026-06-16 16:34:13 +02:00
OpeOginni db43ee0b05 docs: revert localized provider updates 2026-06-16 16:00:21 +02:00
OpeOginni 2b53cf0f4a Merge branch 'dev' into feat/azure-oauth 2026-06-16 15:55:55 +02:00
OpeOginni dffa1045d3 Merge branch 'dev' into feat/azure-oauth 2026-06-08 14:02:33 +02:00
OpeOginni d862d02d0e docs: added more docs on new methods to connect azure and az cognitive 2026-06-08 13:24:28 +02:00
OpeOginni 4abac2a431 feat(opencode): added oauth to azure through MS Entra ID and az cli 2026-06-08 13:23:59 +02:00
155 changed files with 3996 additions and 2059 deletions
+1
View File
@@ -10,6 +10,7 @@ kitlangton
kommander
ludvigrask
MrMushrooooom
neriousy
nexxeln
R44VC0RP
rekram1-node
-37
View File
@@ -1,37 +0,0 @@
name: beta
on:
workflow_dispatch:
schedule:
- cron: "0 * * * *"
jobs:
sync:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Setup Git Committer
id: setup-git-committer
uses: ./.github/actions/setup-git-committer
with:
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
- name: Install OpenCode
run: bun i -g opencode-ai
- name: Sync beta branch
env:
GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
run: bun script/beta.ts
+1
View File
@@ -35,6 +35,7 @@ jobs:
- run: bun sst deploy --stage=${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ github.token }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
+1 -1
View File
@@ -4,7 +4,7 @@ import { tool } from "@opencode-ai/plugin"
const TEAM = {
tui: ["kommander", "simonklee"],
desktop_web: ["Hona", "Brendonovich"],
core: ["jlongster", "rekram1-node", "nexxeln", "kitlangton"],
core: ["jlongster", "rekram1-node", "neriousy", "nexxeln", "kitlangton"],
inference: ["fwang", "MrMushrooooom", "starptech"],
windows: ["Hona"],
} as const
+117 -62
View File
@@ -29,7 +29,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@corvu/drawer": "catalog:",
"@dnd-kit/abstract": "0.5.0",
@@ -96,7 +96,7 @@
},
"packages/cli": {
"name": "@opencode-ai/cli",
"version": "1.18.16",
"version": "1.18.18",
"bin": {
"lildax": "./bin/lildax.cjs",
},
@@ -144,7 +144,7 @@
},
"packages/codemode": {
"name": "@opencode-ai/codemode",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"acorn": "8.15.0",
"effect": "catalog:",
@@ -158,7 +158,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -194,7 +194,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -221,7 +221,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/openai": "3.0.48",
@@ -243,7 +243,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -267,7 +267,7 @@
},
"packages/console/support": {
"name": "@opencode-ai/console-support",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@opencode-ai/console-core": "workspace:*",
@@ -287,7 +287,7 @@
},
"packages/core": {
"name": "@opencode-ai/core",
"version": "1.18.16",
"version": "1.18.18",
"bin": {
"opencode": "./bin/opencode",
},
@@ -332,7 +332,7 @@
"@opentelemetry/sdk-trace-base": "2.6.1",
"@parcel/watcher": "2.5.1",
"@silvia-odwyer/photon-node": "0.3.4",
"ai-gateway-provider": "3.1.2",
"ai-gateway-provider": "3.2.0",
"bun-pty": "0.4.8",
"cross-spawn": "catalog:",
"diff": "catalog:",
@@ -381,7 +381,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@zip.js/zip.js": "2.7.62",
"drizzle-orm": "catalog:",
@@ -435,7 +435,7 @@
},
"packages/effect-drizzle-sqlite": {
"name": "@opencode-ai/effect-drizzle-sqlite",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"drizzle-orm": "catalog:",
"effect": "catalog:",
@@ -449,7 +449,7 @@
},
"packages/effect-sqlite-node": {
"name": "@opencode-ai/effect-sqlite-node",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"effect": "catalog:",
},
@@ -461,7 +461,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@hono/standard-validator": "catalog:",
"@opencode-ai/core": "workspace:*",
@@ -493,7 +493,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -509,7 +509,7 @@
},
"packages/http-recorder": {
"name": "@opencode-ai/http-recorder",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@effect/platform-node": "4.0.0-beta.83",
"@effect/platform-node-shared": "4.0.0-beta.83",
@@ -540,7 +540,7 @@
},
"packages/llm": {
"name": "@opencode-ai/llm",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@opencode-ai/schema": "workspace:*",
"@smithy/eventstream-codec": "4.2.14",
@@ -559,7 +559,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.18.16",
"version": "1.18.18",
"bin": {
"opencode": "./bin/opencode",
},
@@ -623,7 +623,7 @@
"@types/ws": "8.18.1",
"@zip.js/zip.js": "2.7.62",
"ai": "catalog:",
"ai-gateway-provider": "3.1.2",
"ai-gateway-provider": "3.2.0",
"bonjour-service": "1.3.0",
"chokidar": "4.0.3",
"cross-spawn": "catalog:",
@@ -690,7 +690,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@ai-sdk/provider": "3.0.8",
"@opencode-ai/sdk": "workspace:*",
@@ -766,7 +766,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"cross-spawn": "catalog:",
},
@@ -781,7 +781,7 @@
},
"packages/server": {
"name": "@opencode-ai/server",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@opencode-ai/core": "workspace:*",
"@opencode-ai/protocol": "workspace:*",
@@ -796,7 +796,7 @@
},
"packages/session-ui": {
"name": "@opencode-ai/session-ui",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz",
@@ -836,7 +836,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -849,7 +849,7 @@
},
"packages/stats/app": {
"name": "@opencode-ai/stats-app",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@ibm/plex": "6.4.1",
"@kobalte/core": "catalog:",
@@ -883,7 +883,7 @@
},
"packages/stats/core": {
"name": "@opencode-ai/stats-core",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@aws-sdk/client-athena": "3.933.0",
"@planetscale/database": "1.19.0",
@@ -902,7 +902,7 @@
},
"packages/stats/server": {
"name": "@opencode-ai/stats-server",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@aws-sdk/client-firehose": "3.933.0",
"@effect/platform-node": "catalog:",
@@ -944,7 +944,7 @@
},
"packages/tui": {
"name": "@opencode-ai/tui",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@opencode-ai/core": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
@@ -971,7 +971,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@kobalte/core": "catalog:",
"@pierre/diffs": "catalog:",
@@ -1022,7 +1022,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -1075,6 +1075,7 @@
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
"@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
"@ai-sdk/groq@3.0.31": "patches/@ai-sdk%2Fgroq@3.0.31.patch",
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
"@dnd-kit/dom@0.5.0": "patches/@dnd-kit%2Fdom@0.5.0.patch",
@@ -1182,15 +1183,15 @@
"@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OqcCq2PiFY1dbK/0Ck45KuvE8jfdxRuuAE9Y5w46dAk6U+9vPOeg1CDcmR+ncqmrYrhRl3nmyDttyDahyjCzAw=="],
"@ai-sdk/deepgram": ["@ai-sdk/deepgram@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-VscTV68g6sXRY4O1yl72/O8y6+tBDvSQax6bqX06hRKWBGxsJ8Jr3LZsNmZnK9Od5Icx565ijK0QgrlNaN4TdQ=="],
"@ai-sdk/deepgram": ["@ai-sdk/deepgram@2.0.51", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-DPoKiCXDwzopJI/pHcZnXaycZ5qqCL4xCWcfsb1s8M8OUPchK2GUHcCXt6v056fAqtKWLF/hrW+RcHFFY0qyHQ=="],
"@ai-sdk/deepinfra": ["@ai-sdk/deepinfra@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-y6RoOP7DGWmDSiSxrUSt5p18sbz+Ixe5lMVPmdE7x+Tr5rlrzvftyHhjWHfqlAtoYERZTGFbP6tPW1OfQcrb4A=="],
"@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.47", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MzcQ321JO8OY+TVLFI81A7cIIuoeLLxrLCDD+8C1E3Ro6UFyfMtRXo9bw9OhTMRSDMo6hgSDOo4Fekz8aJtQYQ=="],
"@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EtvsWfGrqx3OhzJdoi82qH+4yzEPPKZr2utyQ+w8cHKoFeg0+8Lou9Z3uixy73WEwz8Z1+AR8QT9fZ64AWGYPA=="],
"@ai-sdk/elevenlabs": ["@ai-sdk/elevenlabs@2.0.51", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XFONX6rAsu6d13cJVUfZfkq4a+qdThlxvoEfzYlSRa1AvALlzwX7Y6bunXxevuifT3n882+nDdWrdiYvFP0+Fw=="],
"@ai-sdk/fireworks": ["@ai-sdk/fireworks@2.0.53", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-HjeiGsdxSzrCkOf2l2V+K+opzlqxBtduBq6BCiohAdgQk2KdZmI/67SMkBM6Kdze/BjUXiZlv0d7zNICPhxVDA=="],
"@ai-sdk/fireworks": ["@ai-sdk/fireworks@2.0.76", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.67", "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yg1ulgemh6BLMrs2vBNbtjV70NhenFI3Z7psB7s94FOEcGevvyV1qdFoqsgBZ7QyuUGwnC645c+eBFFtPAr5SQ=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.104", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA=="],
@@ -3062,7 +3063,7 @@
"ai": ["ai@6.0.168", "", { "dependencies": { "@ai-sdk/gateway": "3.0.104", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ=="],
"ai-gateway-provider": ["ai-gateway-provider@3.1.2", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.62", "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/azure": "^3.0.31", "@ai-sdk/cerebras": "^2.0.34", "@ai-sdk/cohere": "^3.0.21", "@ai-sdk/deepgram": "^2.0.20", "@ai-sdk/deepseek": "^2.0.20", "@ai-sdk/elevenlabs": "^2.0.20", "@ai-sdk/fireworks": "^2.0.34", "@ai-sdk/google": "^3.0.30", "@ai-sdk/google-vertex": "^4.0.61", "@ai-sdk/groq": "^3.0.24", "@ai-sdk/mistral": "^3.0.20", "@ai-sdk/openai": "^3.0.30", "@ai-sdk/perplexity": "^3.0.19", "@ai-sdk/xai": "^3.0.57", "@openrouter/ai-sdk-provider": "^2.2.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^2.0.0", "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.0", "ai": "^6.0.0" } }, "sha512-krGNnJSoO/gJ7Hbe5nQDlsBpDUGIBGtMQTRUaW7s1MylsfvLduba0TLWzQaGtOmNRkP0pGhtGlwsnS6FNQMlyw=="],
"ai-gateway-provider": ["ai-gateway-provider@3.2.0", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.117", "@ai-sdk/anthropic": "^3.0.84", "@ai-sdk/azure": "^3.0.74", "@ai-sdk/cerebras": "^2.0.56", "@ai-sdk/cohere": "^3.0.38", "@ai-sdk/deepgram": "^2.0.35", "@ai-sdk/deepseek": "^2.0.38", "@ai-sdk/elevenlabs": "^2.0.35", "@ai-sdk/fireworks": "^2.0.56", "@ai-sdk/google": "^3.0.82", "@ai-sdk/google-vertex": "^4.0.145", "@ai-sdk/groq": "^3.0.41", "@ai-sdk/mistral": "^3.0.39", "@ai-sdk/openai": "^3.0.71", "@ai-sdk/perplexity": "^3.0.35", "@ai-sdk/xai": "^3.0.95", "@openrouter/ai-sdk-provider": "^2.10.0" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^2.0.0", "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.0", "ai": "^6.0.0" } }, "sha512-IGSV96IqAfiZd20CWSMVQk5sVeLcJR2uQcoWLB8GdkxyvQrsU3x4U0o1Ok6bfVJug7SrlX6I8ibz9cHIkUyRtg=="],
"ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
@@ -5666,9 +5667,9 @@
"@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
"@ai-sdk/deepgram/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"@ai-sdk/deepgram/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"@ai-sdk/deepgram/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"@ai-sdk/deepgram/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"@ai-sdk/deepinfra/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
@@ -5676,15 +5677,15 @@
"@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q=="],
"@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.67", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-glcEJC2mBXJKj7joFI0fRhcbdDYKTBgXMPcT6Vcnlym67tTzuNG9pFx3zblxVv8TdOxhojJja5zGG19yeGJxuA=="],
"@ai-sdk/fireworks/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"@ai-sdk/fireworks/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"@ai-sdk/fireworks/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"@ai-sdk/fireworks/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"@ai-sdk/google/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
@@ -6170,21 +6171,25 @@
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.107", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.78", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8nT08pGPy25rleJNk56ep00UHK6kCtCmu+ZNqVVSSPDieADlIZqcaN1iRXAFBoCH0Fb9F6C2EjFDaySdsargfQ=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.153", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.110", "@ai-sdk/openai": "3.0.96", "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iEXrLgWylCHJmznqlKLU3CqRh8UWibv+illrwmsk136FVBBvyXiGnpQrI1pGWCScVLQjBQSFQu7GJDkUEomf/A=="],
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.78", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0OY12G20cUt6iU6htpEA1491Oz++NVxZxlmWGX4B7rSbeZ5pnDmOu6YtW9BKzdZlNx5Gn23i6WMxyZFoMKNcgA=="],
"ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.110", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rNkamQCeAUOUGr5Npg5pXZyYFH4fS1U6Mbdy3dF/NNBEI3D2Chc/ruRrwNegP0gfpX3cllP3O4jSibGBbWPZ7A=="],
"ai-gateway-provider/@ai-sdk/azure": ["@ai-sdk/azure@3.0.49", "", { "dependencies": { "@ai-sdk/openai": "3.0.48", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wskgAL+OmrHG7by/iWIxEBQCEdc1mDudha/UZav46i0auzdFfsDB/k2rXZaC4/3nWSgMZkxr0W3ncyouEGX/eg=="],
"ai-gateway-provider/@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.60", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.54", "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Rnok3cThg6awBwaDSyiZpgRpbV7pqxGYrA89LODCo5cuEHeP2h0AM0lLHP7zIkclAdXfOm4wldKi/S2T/DGCOw=="],
"ai-gateway-provider/@ai-sdk/deepseek": ["@ai-sdk/deepseek@2.0.35", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9DhYurbAvcurOEGN6u2myYDybrrzGfcrkG8hwmFjwTrePW6KCMggm0YxP7e8RkLYcQKqCEMgFlyEB4BM6EmiKg=="],
"ai-gateway-provider/@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cXLjIsSzUriPHe704IH6d+ipJ/OvczTB700p9Zma7DPgQzvxG/diyr8q/2LEsbTRiTopiKhky8dn1PJNQcJToQ=="],
"ai-gateway-provider/@ai-sdk/mistral": ["@ai-sdk/mistral@3.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZXe7nZQgliDdjz5ufH5RKpHWxbN72AzmzzKGbF/z+0K9GN5tUCnftrQRvTRFHA5jAzTapcm2BEevmGLVbMkW+A=="],
"ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@3.0.108", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kwvYpRNghqt0VRKE7Hx1UWZQCUJJFqUITj24baxy+ApS0Hru0PkBJHD75a36Wc+e6e+wHcKR2MconTeJiBZigA=="],
"ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="],
"ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.181", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.110", "@ai-sdk/google": "3.0.108", "@ai-sdk/openai-compatible": "2.0.67", "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-57b5Qor8V53vubkxCj09tbHWpzpCLUbzmll2FwShuLvyEAsCH6mh3sAowDhiwUWPXnLzU+rC3RVMKCPscqICcg=="],
"ai-gateway-provider/@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="],
"ai-gateway-provider/@ai-sdk/groq": ["@ai-sdk/groq@3.0.59", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-X4h60TGq4pIOXPsthatUr+bfTaYCaKGX597hG9JgcueEl4+nboCdw99ixjFKGkvYlBJwLCCfI957EmGA2QlF0w=="],
"ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.8.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Y6j3yivgoEUf/kutD/k5GX/mzZfioRFoSx0gbQ+mIOzMaH/vJv1rCkztiuvlLw5xRYQil7oxHUZvmSfXqOx1NQ=="],
"ai-gateway-provider/@ai-sdk/openai": ["@ai-sdk/openai@3.0.84", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.38" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cmgbeJL0bbY0yTJH4/AdmP5E7MjWRL9G8UdhIi0JlV/So03o82ORJofW8OzwCZPTORVQblFbpZXYGDcUd9NdUQ=="],
"ai-gateway-provider/@ai-sdk/perplexity": ["@ai-sdk/perplexity@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bL3SWrPltTuxVNg/bZ5APbJLVe0+BIU+BYAbT8Yo0eSAZ5eMTImN2murQHZa08Wi8lUMm8MNolaYBQkb0JDbFw=="],
"ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.10.0", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-FMsAEjLUt5pWuRE2LDC/LCvVrFjLlrEzUITH5+5SZtfq7KZ2wrOHjQVxzz92sju8S9ltpzW87CLW8/b0oBXVCw=="],
"ajv-keywords/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
@@ -6594,14 +6599,20 @@
"@ai-sdk/deepgram/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/deepgram/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"@ai-sdk/deepinfra/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/elevenlabs/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"@ai-sdk/fireworks/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/fireworks/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"@ai-sdk/google-vertex/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
@@ -6976,29 +6987,51 @@
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/openai": ["@ai-sdk/openai@3.0.96", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Pex8vOj1y05j7jtBS39cJJRDjJbMIyCY9+01cSIp1hwEJTKImrFejMgsAazMWXSi/HU+B9ZE6ElftCOwvg4mmQ=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ALmj/53EXpcRqMbGpPJPP4UOSWw0q4VGpnDo7YctvsynjkrKDmoneDG/1a7VQnSPYHnJp6tTRMf5ZdxZ5whulg=="],
"ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OyXt0zK8y2/ZIyWlbxTv2r1M7AK227S+Gl4BYOEF42q0wz1n5m4fwR8L4Fy/MQ4Ho6xje47MPsFcRdIqIyP6Rw=="],
"ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
"ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="],
"ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
"ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-nJ0bAfegMAIJtrzMJtbzer1cS3nb7c7DsyU1S4nrPm7ZU0Mn6SBBZv5IGZZGTbpWTJwqKTSPeZJTXalbAxt1BA=="],
"ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
"ai-gateway-provider/@ai-sdk/cohere/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
"ai-gateway-provider/@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/xai/@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=="],
"ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.67", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.45" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-glcEJC2mBXJKj7joFI0fRhcbdDYKTBgXMPcT6Vcnlym67tTzuNG9pFx3zblxVv8TdOxhojJja5zGG19yeGJxuA=="],
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/groq/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/HHGmtKllqjg1OLc023v9w9kK3laW7Z6TzfZukYQWCsGBbzB9p60zTvvpXFVcs44NZBVXL3viOa1HRKUbeee8g=="],
"ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^5.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7u5B/E2uZmU65SlJhhQGFHZwRCN0xOz4HHtFc4sEGV9PHbX3fGiEiZBpc/SABay1dGeJgK3VD60rvLGoWdWPXA=="],
"ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
@@ -7418,13 +7451,35 @@
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"ai-gateway-provider/@ai-sdk/deepseek/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/mistral/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/cohere/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/cohere/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"ai-gateway-provider/@ai-sdk/groq/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/groq/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"ai-gateway-provider/@ai-sdk/perplexity/@ai-sdk/provider-utils/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+13 -1
View File
@@ -181,6 +181,16 @@ const statsSyncConfig = new sst.Linkable("StatsSyncConfig", {
},
})
const r2SqlAuthToken = new sst.Secret("R2SqlAuthToken")
const r2Sql = new sst.Linkable("R2Sql", {
properties: {
accountId: "15d29c8639fd3733b1b5486a2acfd968",
bucket: `platform-${$app.stage}-lake`,
namespace: "inference",
table: "generation",
},
})
export const statSync = new sst.aws.Service("StatsSyncService", {
cluster: lakeCluster,
architecture: "arm64",
@@ -193,7 +203,9 @@ export const statSync = new sst.aws.Service("StatsSyncService", {
dockerfile: "packages/stats/server/Dockerfile",
},
command: ["bun", "src/stat-sync.ts"],
link: [database, inferenceEvent, statsSyncConfig],
// Keep the legacy Athena link and IAM permissions during the first R2-backed
// release so reverting the application code remains a one-deploy rollback.
link: [database, inferenceEvent, r2Sql, r2SqlAuthToken, statsSyncConfig],
permissions: lakeQueryPermissions,
scaling: {
min: 1,
+4 -4
View File
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-uduwrM143NDSc+tXsi4lVVfoMll2a3BDHRUjuO7GB68=",
"aarch64-linux": "sha256-6DUda78XdXY6DP86lIUkweSjys3iG4Y4mo1PiaNuXbg=",
"aarch64-darwin": "sha256-AkJwfLULLZVwwz+XU1QcFUZoIS7oVPCn+n/MXEaxrqE=",
"x86_64-darwin": "sha256-hAxKGdiITTxQ2uujQt6prNjo3NxGAMMeo+9HlMWK6GU="
"x86_64-linux": "sha256-kDCnJMnaK/Jq7ckcpPB7Vl9v98EMSdcehZAtf8jNjTs=",
"aarch64-linux": "sha256-0aR+OJGXS5HMlXbe/BHybjIRvdNJJw6gjW+jr6Dk7Pk=",
"aarch64-darwin": "sha256-loLrV6xiorhwS/N2hlpiKSKX172Qxy+auNiPzFBhQSc=",
"x86_64-darwin": "sha256-PNEpQBLAz8M274bSyTpp0jofETn2L+D0uBiJHUV7nB0="
}
}
+2 -1
View File
@@ -159,6 +159,7 @@
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
"effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
"@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch",
"@ai-sdk/openai-compatible@2.0.41": "patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch"
"@ai-sdk/openai-compatible@2.0.41": "patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch",
"@ai-sdk/groq@3.0.31": "patches/@ai-sdk%2Fgroq@3.0.31.patch"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.18.16",
"version": "1.18.18",
"description": "",
"type": "module",
"exports": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/cli",
"version": "1.18.16",
"version": "1.18.18",
"type": "module",
"license": "MIT",
"bin": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/codemode",
"version": "1.18.16",
"version": "1.18.18",
"description": "Effect-native confined code execution over schema-described tools",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.18.16",
"version": "1.18.18",
"type": "module",
"license": "MIT",
"scripts": {
-1
View File
@@ -252,7 +252,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "الاستثناءات التالية",
"go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع",
"go.banner.text": "يحصل DeepSeek V4 Flash على حدود استخدام مضاعفة لفترة محدودة",
"go.meta.description":
"يبدأ Go بسعر $5 للشهر الأول، ثم $10/شهر، مع حدود استخدام سخية ووصول موثوق إلى نماذج البرمجة الرائدة.",
"go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع",
-1
View File
@@ -256,7 +256,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "seguintes exceções",
"go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos",
"go.banner.text": "DeepSeek V4 Flash tem limites de uso 2x maiores por tempo limitado",
"go.meta.description":
"O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de uso e acesso confiável aos principais modelos de codificação.",
"go.hero.title": "Modelos de codificação de baixo custo para todos",
-1
View File
@@ -254,7 +254,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "følgende undtagelser",
"go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle",
"go.banner.text": "DeepSeek V4 Flash får fordoblet brugsgrænse i en begrænset periode",
"go.meta.description":
"Go starter ved $5 for den første måned, derefter $10/måned, med generøse brugsgrænser og pålidelig adgang til førende kodningsmodeller.",
"go.hero.title": "Kodningsmodeller til lav pris for alle",
-1
View File
@@ -256,7 +256,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "folgenden Ausnahmen",
"go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle",
"go.banner.text": "DeepSeek V4 Flash erhält für begrenzte Zeit 2x Nutzungslimits",
"go.meta.description":
"Go beginnt bei $5 für deinen ersten Monat, danach $10/Monat, mit großzügigen Nutzungslimits und zuverlässigem Zugang zu führenden Coding-Modellen.",
"go.hero.title": "Kostengünstige Coding-Modelle für alle",
+2 -2
View File
@@ -253,7 +253,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "following exceptions",
"go.title": "OpenCode Go | Low cost coding models for everyone",
"go.banner.text": "DeepSeek V4 Flash gets 2× usage limits for a limited time",
"go.meta.description":
"Go starts at $5 for your first month, then $10/month, with generous usage limits and reliable access to leading coding models.",
"go.hero.title": "Low cost coding models for everyone",
@@ -373,7 +372,8 @@ export const dict = {
"zen.api.error.providerNotSupported": "Provider {{provider}} not supported",
"zen.api.error.missingApiKey": "Missing API key.",
"zen.api.error.invalidApiKey": "Invalid API key.",
"zen.api.error.requestBlockedByUpstreamProvider": "Request blocked by upstream provider.",
"zen.api.error.requestBlockedByUpstreamProvider":
"This account has found to be committing fraud or is in breach of terms of services and has been blocked. If you believe this is a mistake please send an email to help@anoma.ly.",
"zen.api.error.subscriptionQuotaExceeded": "Subscription quota exceeded. Retry in {{retryIn}}.",
"zen.api.error.goSubscriptionRollingLimitExceeded":
"5-hour usage limit reached. Resets in {{retryIn}}. To continue using this model now, enable usage from your available balance: {{consoleGoUrl}}",
-1
View File
@@ -257,7 +257,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "siguientes excepciones",
"go.title": "OpenCode Go | Modelos de programación de bajo coste para todos",
"go.banner.text": "DeepSeek V4 Flash tiene límites de uso 2x mayores por tiempo limitado",
"go.meta.description":
"Go comienza en $5 el primer mes, luego 10 $/mes, con límites de uso generosos y acceso fiable a modelos de programación líderes.",
"go.hero.title": "Modelos de programación de bajo coste para todos",
-1
View File
@@ -258,7 +258,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "exceptions suivantes",
"go.title": "OpenCode Go | Modèles de code à faible coût pour tous",
"go.banner.text": "DeepSeek V4 Flash bénéficie de limites dutilisation 2x supérieures pour une durée limitée",
"go.meta.description":
"Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites d'utilisation généreuses et un accès fiable aux principaux modèles de codage.",
"go.hero.title": "Modèles de code à faible coût pour tous",
-1
View File
@@ -254,7 +254,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "seguenti eccezioni",
"go.title": "OpenCode Go | Modelli di coding a basso costo per tutti",
"go.banner.text": "DeepSeek V4 Flash offre limiti di utilizzo 2x superiori per un periodo limitato",
"go.meta.description":
"Go inizia a $5 per il primo mese, poi $10/mese, con limiti di utilizzo generosi e un accesso affidabile ai principali modelli di coding.",
"go.hero.title": "Modelli di coding a basso costo per tutti",
-1
View File
@@ -253,7 +253,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "以下の例外",
"go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル",
"go.banner.text": "DeepSeek V4 Flashの利用上限が期間限定で2倍に",
"go.meta.description":
"Goは最初の月$5、その後$10/月で、主要なコーディングモデルへのゆとりある利用上限と安定したアクセスを提供します。",
"go.hero.title": "すべての人のための低価格なコーディングモデル",
-1
View File
@@ -250,7 +250,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "다음 예외",
"go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델",
"go.banner.text": "DeepSeek V4 Flash 사용 한도가 한시적으로 2배 확대됩니다",
"go.meta.description":
"Go는 첫 달 $5, 이후 $10/월로 시작하며, 넉넉한 사용 한도와 주요 코딩 모델에 대한 안정적인 액세스를 제공합니다.",
"go.hero.title": "모두를 위한 저비용 코딩 모델",
-1
View File
@@ -254,7 +254,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "følgende unntak",
"go.title": "OpenCode Go | Rimelige kodemodeller for alle",
"go.banner.text": "DeepSeek V4 Flash får 2x bruksgrense i en begrenset periode",
"go.meta.description":
"Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse bruksgrenser og pålitelig tilgang til ledende kodemodeller.",
"go.hero.title": "Rimelige kodemodeller for alle",
-1
View File
@@ -255,7 +255,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "następującymi wyjątkami",
"go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego",
"go.banner.text": "DeepSeek V4 Flash oferuje 2x wyższe limity użycia przez ograniczony czas",
"go.meta.description":
"Go kosztuje $5 za pierwszy miesiąc, a następnie $10/miesiąc, oferując hojne limity użycia i niezawodny dostęp do wiodących modeli do kodowania.",
"go.hero.title": "Niskokosztowe modele do kodowania dla każdego",
-1
View File
@@ -258,7 +258,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "следующими исключениями",
"go.title": "OpenCode Go | Недорогие модели для кодинга для всех",
"go.banner.text": "DeepSeek V4 Flash получает 2x лимиты использования на ограниченное время",
"go.meta.description":
"Go стоит $5 за первый месяц, затем $10/месяц и предлагает щедрые лимиты использования и надежный доступ к ведущим моделям для кодинга.",
"go.hero.title": "Недорогие модели для кодинга для всех",
-1
View File
@@ -253,7 +253,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "ข้อยกเว้นดังนี้",
"go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
"go.banner.text": "DeepSeek V4 Flash เพิ่มโควตาการใช้งานเป็น 2 เท่าในช่วงเวลาจำกัด",
"go.meta.description":
"Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดการใช้งานที่เอื้อเฟื้อและการเข้าถึงโมเดลเขียนโค้ดชั้นนำอย่างเชื่อถือได้",
"go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
-1
View File
@@ -256,7 +256,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "aşağıdaki istisnalar",
"go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri",
"go.banner.text": "DeepSeek V4 Flash sınırlı bir süre için 2x kullanım limiti sunuyor",
"go.meta.description":
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; cömert kullanım limitleri ve önde gelen kodlama modellerine güvenilir erişim sunar.",
"go.hero.title": "Herkes için düşük maliyetli kodlama modelleri",
-1
View File
@@ -255,7 +255,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "такими винятками",
"go.title": "OpenCode Go | Недорогі моделі кодування для всіх",
"go.banner.text": "DeepSeek V4 Flash отримує 2x ліміти використання протягом обмеженого часу",
"go.meta.description":
"Go починається від $5 за перший місяць, потім $10/місяць, зі щедрими лімітами використання та надійним доступом до провідних моделей для кодування.",
"go.hero.title": "Недорогі моделі кодування для всіх",
-1
View File
@@ -244,7 +244,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "以下例外情况除外",
"go.title": "OpenCode Go | 人人可用的低成本编程模型",
"go.banner.text": "DeepSeek V4 Flash 限时享受 2 倍使用额度",
"go.meta.description": "Go 首月 $5,之后 $10/月,提供充裕的使用限额,并可可靠访问领先的编程模型。",
"go.hero.title": "人人可用的低成本编程模型",
"go.hero.body":
-1
View File
@@ -244,7 +244,6 @@ export const dict = {
"zen.privacy.exceptionsLink": "以下例外情況",
"go.title": "OpenCode Go | 低成本全民編碼模型",
"go.banner.text": "DeepSeek V4 Flash 限時享有 2 倍使用額度",
"go.meta.description": "Go 首月 $5,之後 $10/月,提供充裕的使用限額,並可穩定存取領先的編碼模型。",
"go.hero.title": "低成本全民編碼模型",
"go.hero.body":
+6 -18
View File
@@ -25,6 +25,7 @@ const checkLoggedIn = query(async () => {
const models = [
{ name: "Grok 4.5", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention30" },
{ name: "GPT 5.6 Luna", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention30" },
{ name: "GLM-5.3", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
{ name: "GLM-5.2", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
{ name: "GLM-5.1", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
{ name: "Kimi K3", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
@@ -66,24 +67,17 @@ function LimitsGraph(props: { href: string }) {
const baseline = 100
const graph = [
{ id: "grok-4.5", name: "Grok 4.5", req: 120, d: "50ms" },
{ id: "kimi-k3", name: "Kimi K3", req: 110, d: "75ms" },
{ id: "kimi-k3", name: "Kimi K3", req: 110, d: "50ms" },
{ id: "grok-4.5", name: "Grok 4.5", req: 120, d: "75ms" },
{ id: "qwen3.8-max", name: "Qwen3.8 Max", req: 160, d: "90ms" },
{ id: "glm-5.2", name: "GLM-5.2", req: 880, d: "100ms" },
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", req: 1050, d: "150ms" },
{ id: "minimax-m3", name: "MiniMax M3", req: 3200, d: "210ms" },
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", req: 3450, d: "270ms" },
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 3800, d: "270ms" },
{ id: "gpt-5.6-luna", name: "GPT 5.6 Luna", req: 4100, baseReq: 2050, d: "290ms" },
{ id: "qwen3.7-plus", name: "Qwen3.7 Plus", req: 4300, d: "300ms" },
{ id: "hy3", name: "Hy3", req: 4300, d: "320ms" },
{ id: "mimo-v2.5", name: "MiMo-V2.5", req: 30100, d: "340ms" },
{
id: "deepseek-v4-flash",
name: "DeepSeek V4 Flash",
req: 63300,
baseReq: 31650,
edge: true,
d: "340ms",
},
{ id: "mimo-v2.5", name: "MiMo-V2.5", req: 30100, edge: true, d: "340ms" },
]
const w = 1040
@@ -258,12 +252,6 @@ export default function Home() {
<div data-component="content">
<section data-component="hero">
<div data-component="desktop-app-banner">
<span data-slot="badge">{i18n.t("home.banner.badge")}</span>
<div data-slot="content">
<span data-slot="text">{i18n.t("go.banner.text")}</span>
</div>
</div>
<div data-slot="hero-copy">
<img data-slot="zen logo light" src={goLogoLight} alt="" />
<img data-slot="zen logo dark" src={goLogoDark} alt="" />
@@ -306,6 +306,7 @@ export function LiteSection(props: { lite: LiteSubscription | undefined }) {
<ul data-slot="promo-models">
<li>Grok 4.5</li>
<li>GPT 5.6 Luna</li>
<li>GLM-5.3</li>
<li>GLM-5.2</li>
<li>GLM-5.1</li>
<li>Kimi K3</li>
@@ -0,0 +1,158 @@
import type { APIEvent } from "@solidjs/start/server"
import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js"
import { BillingTable, LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js"
import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
import { LiteData } from "@opencode-ai/console-core/lite.js"
import { Subscription } from "@opencode-ai/console-core/subscription.js"
export async function GET(input: APIEvent) {
const apiKey = input.request.headers.get("authorization")?.match(/^Bearer (\S+)$/)?.[1]
if (!apiKey) {
return new Response(
JSON.stringify({
type: "error",
error: {
type: "AuthError",
message: "Missing API key.",
},
}),
{
status: 401,
headers: {
"Content-Type": "application/json",
},
},
)
}
const auth = await Database.use((tx) =>
tx
.select({
userID: KeyTable.userID,
workspaceID: KeyTable.workspaceID,
})
.from(KeyTable)
.innerJoin(
UserTable,
and(
eq(UserTable.workspaceID, KeyTable.workspaceID),
eq(UserTable.id, KeyTable.userID),
isNull(UserTable.timeDeleted),
),
)
.innerJoin(WorkspaceTable, and(eq(WorkspaceTable.id, KeyTable.workspaceID), isNull(WorkspaceTable.timeDeleted)))
.innerJoin(
BillingTable,
and(eq(BillingTable.workspaceID, KeyTable.workspaceID), isNull(BillingTable.timeDeleted)),
)
.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
.then((rows) => rows[0]),
)
if (!auth) {
return new Response(
JSON.stringify({
type: "error",
error: {
type: "AuthError",
message: "Unauthorized",
},
}),
{
status: 401,
headers: {
"Content-Type": "application/json",
},
},
)
}
const row = await Database.use((tx) =>
tx
.select({
timeCreated: LiteTable.timeCreated,
rollingUsage: LiteTable.rollingUsage,
weeklyUsage: LiteTable.weeklyUsage,
monthlyUsage: LiteTable.monthlyUsage,
timeRollingUpdated: LiteTable.timeRollingUpdated,
timeWeeklyUpdated: LiteTable.timeWeeklyUpdated,
timeMonthlyUpdated: LiteTable.timeMonthlyUpdated,
})
.from(LiteTable)
.where(
and(
eq(LiteTable.workspaceID, auth.workspaceID),
eq(LiteTable.userID, auth.userID),
isNull(LiteTable.timeDeleted),
),
)
.then((rows) => rows[0]),
)
if (!row) {
return new Response(
JSON.stringify({
type: "error",
error: {
type: "EntitlementError",
message: "OpenCode Go subscription required.",
},
}),
{
status: 403,
headers: {
"Content-Type": "application/json",
},
},
)
}
const limits = LiteData.getLimits()
return new Response(
JSON.stringify({
usage: {
rolling: formatUsage(
Subscription.analyzeRollingUsage({
limit: limits.rollingLimit,
window: limits.rollingWindow,
usage: row.rollingUsage ?? 0,
timeUpdated: row.timeRollingUpdated ?? new Date(),
}),
),
weekly: formatUsage(
Subscription.analyzeWeeklyUsage({
limit: limits.weeklyLimit,
usage: row.weeklyUsage ?? 0,
timeUpdated: row.timeWeeklyUpdated ?? new Date(),
}),
),
monthly: formatUsage(
Subscription.analyzeMonthlyUsage({
limit: limits.monthlyLimit,
usage: row.monthlyUsage ?? 0,
timeUpdated: row.timeMonthlyUpdated ?? new Date(),
timeSubscribed: row.timeCreated,
}),
),
},
}),
{
status: 200,
headers: {
"Content-Type": "application/json",
},
},
)
}
function formatUsage(usage: { status: "ok" | "rate-limited"; resetInSec: number; usagePercent: number }) {
return {
status: usage.status,
percent: usage.usagePercent,
resetsAt: new Date(Date.now() + usage.resetInSec * 1000).toISOString(),
}
}
@@ -139,7 +139,7 @@ export async function handler(
if (
authInfo &&
opts.modelList === "lite" &&
modelInfo.id === "deepseek-v4-flash" &&
["deepseek-v4-flash", "deepseek-v4-pro"].includes(modelInfo.id) &&
!allowedRegions?.includes("cn")
)
throw new RegionError(
@@ -1032,11 +1032,14 @@ export async function handler(
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
usageInfo
const hour = new Date().getUTCHours()
const modelCost =
modelInfo.cost200K &&
inputTokens + (cacheReadTokens ?? 0) + (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0) > 200_000
? modelInfo.cost200K
: modelInfo.cost
modelInfo.costPeak && ((hour >= 1 && hour < 4) || (hour >= 6 && hour < 10))
? modelInfo.costPeak
: modelInfo.cost200K &&
inputTokens + (cacheReadTokens ?? 0) + (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0) > 200_000
? modelInfo.cost200K
: modelInfo.cost
const inputCost = modelCost.input * inputTokens * 100
const outputCost = modelCost.output * outputTokens * 100
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.18.16",
"version": "1.18.18",
"private": true,
"type": "module",
"license": "MIT",
+1
View File
@@ -24,6 +24,7 @@ export namespace ZenData {
cost: ModelCostSchema,
costMultiplier: z.number().default(1),
cost200K: ModelCostSchema.optional(),
costPeak: ModelCostSchema.optional(),
allowAnonymous: z.boolean().optional(),
byokProvider: z.enum(["openai", "anthropic", "google"]).optional(),
stickyProvider: z.enum(["strict", "prefer"]).optional(),
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.18.16",
"version": "1.18.18",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.18.16",
"version": "1.18.18",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-support",
"version": "1.18.16",
"version": "1.18.18",
"type": "module",
"license": "MIT",
"scripts": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.16",
"version": "1.18.18",
"name": "@opencode-ai/core",
"type": "module",
"license": "MIT",
@@ -101,7 +101,7 @@
"@parcel/watcher": "2.5.1",
"@silvia-odwyer/photon-node": "0.3.4",
"@openrouter/ai-sdk-provider": "2.9.0",
"ai-gateway-provider": "3.1.2",
"ai-gateway-provider": "3.2.0",
"bun-pty": "0.4.8",
"cross-spawn": "catalog:",
"diff": "catalog:",
@@ -5,9 +5,6 @@ export default {
id: "20260410174513_workspace-name",
up(tx) {
return Effect.gen(function* () {
const columns = yield* tx.all<{ name: string }>(`PRAGMA table_info(\`workspace\`)`)
const name = columns.some((column) => column.name === "name") ? "`name`" : "''"
yield* tx.run(`PRAGMA foreign_keys=OFF;`)
yield* tx.run(`
CREATE TABLE \`__new_workspace\` (
@@ -22,7 +19,7 @@ export default {
);
`)
yield* tx.run(
`INSERT INTO \`__new_workspace\`(\`id\`, \`type\`, \`branch\`, \`name\`, \`directory\`, \`extra\`, \`project_id\`) SELECT \`id\`, \`type\`, \`branch\`, ${name}, \`directory\`, \`extra\`, \`project_id\` FROM \`workspace\`;`,
`INSERT INTO \`__new_workspace\`(\`id\`, \`type\`, \`branch\`, \`name\`, \`directory\`, \`extra\`, \`project_id\`) SELECT \`id\`, \`type\`, \`branch\`, \`name\`, \`directory\`, \`extra\`, \`project_id\` FROM \`workspace\`;`,
)
yield* tx.run(`DROP TABLE \`workspace\`;`)
yield* tx.run(`ALTER TABLE \`__new_workspace\` RENAME TO \`workspace\`;`)
+2 -6
View File
@@ -30,15 +30,11 @@ Guidelines:
Complete the user's search request efficiently and report your findings clearly.`
const PROMPT_COMPACTION = `You are an anchored context summarization assistant for coding sessions.
Summarize only the conversation history you are given. The newest turns may be kept verbatim outside your summary, so focus on the older context that still matters for continuing the work.
If the prompt includes a <previous-summary> block, treat it as the current anchored summary. Update it with the new history by preserving still-true details, removing stale details, and merging in new facts.
const PROMPT_COMPACTION = `You are a context summarization agent. You are given a conversation between a user and an agent. Your goal is to produce a structured summary matching the format specified so another coding agent can continue the work.
Always follow the exact output structure requested by the user prompt. Keep every section, preserve exact file paths and identifiers when known, and prefer terse bullets over paragraphs.
Do not answer the conversation itself. Do not mention that you are summarizing, compacting, or merging context. Respond in the same language as the conversation.`
Do not continue the conversation. Do not respond to any questions in the conversation. Only output the structured summary in the exact format requested by the user prompt. Respond in the same language as the conversation.`
const PROMPT_TITLE = `You are a title generator. You output ONLY a thread title. Nothing else.
@@ -24,9 +24,15 @@ export const CloudflareAIGatewayPlugin = define({
apiKey: config.apiKey,
options: gatewayOptions(evt.options, metadata),
} as any)
const unified = createUnified({ apiKey: config.apiKey })
evt.sdk = {
languageModel(modelID: string) {
// Workers AI is the only first-party provider whose upstream is Cloudflare itself, so it is
// the only one that should receive the Cloudflare token as its upstream Authorization header.
// The Unified API addresses Workers AI both with the explicit "workers-ai/" prefix and as
// bare "@cf/..." ids. Third-party providers must not receive the token; they rely on the
// gateway's stored/BYOK keys instead.
const isWorkersAi = modelID.startsWith("workers-ai/") || modelID.startsWith("@cf/")
const unified = createUnified(isWorkersAi ? { apiKey: config.apiKey } : {})
return gateway(unified(modelID))
},
}
@@ -13,7 +13,7 @@ import { ConfigProviderV1 } from "../../v1/config/provider"
import { ConfigProviderOptionsV1 } from "../../v1/config/provider-options"
import { ConfigV1 } from "../../v1/config/config"
const defaultServer = "https://console.opencode.ai"
const defaultServer = "https://opencode.ai/console"
const clientID = "opencode-cli"
const methodID = Integration.MethodID.make("device")
const RemoteResponse = Schema.Struct({ config: ConfigV1.Info })
@@ -40,7 +40,7 @@ already-loaded config until then.
| Scope | Path |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Project config | `./opencode.json`, `./opencode.jsonc`, or `.opencode/opencode.json` (opencode walks up from the cwd to the worktree root) |
| Global config | `~/.config/opencode/opencode.json` (NOT `~/.opencode/`) |
| Global config | `~/.config/opencode/opencode.json` or `~/.config/opencode/opencode.jsonc` (NOT `~/.opencode/`) |
| Project agents | `.opencode/agent/<name>.md` or `.opencode/agents/<name>.md` |
| Global agents | `~/.config/opencode/agent(s)/<name>.md` |
| Project commands | `.opencode/command/<name>.md` or `.opencode/commands/<name>.md` |
+4 -1
View File
@@ -264,7 +264,10 @@ const layer = Layer.effect(
}),
line: match.line_number,
offset: match.absolute_offset,
text: match.lines.text.length > 2_000 ? match.lines.text.slice(0, 2_000) + "..." : match.lines.text,
text:
match.lines.text.length > 2_000
? match.lines.text.slice(0, 2_000).replace(/[\uD800-\uDBFF]$/, "") + "..."
: match.lines.text,
submatches: match.submatches.map((submatch) => ({
text: submatch.match.text,
start: submatch.start,
+25 -19
View File
@@ -44,6 +44,15 @@ Rules:
- Use terse bullets, not prose paragraphs.
- Preserve exact file paths, symbols, commands, error strings, URLs, and identifiers when known.
- Do not mention the summary process or that context was compacted.`
const SUMMARY_UPDATE_INSTRUCTIONS = `The <prior-summary> summarizes everything that happened before the <conversation>. Construct a new summary that combines both. The <prior-summary> is discarded after this: anything you do not carry into the new summary is lost.
When combining:
- Carry forward objectives, constraints, user directives, decisions, and parallel workstreams from the <prior-summary> even when the <conversation> does not mention them. Drop only what is finished and no longer needed.
- The <conversation> is more recent than the <prior-summary>. Where they conflict, the conversation wins: state the corrected fact and drop the old claim.
- Add new progress, decisions, constraints, and context from the conversation.
- Move completed work from "Active" to "Completed".
- If a blocker has been resolved, update the summary to reflect that while keeping any details still needed to continue the work.
- Update "Objective" and "Next Move" to reflect the current work state.`
type Entry = {
readonly seq: number
@@ -136,36 +145,33 @@ const select = (
if (conversation.length === 0) return
let total = 0
let split = conversation.length
let splitPrefix = ""
let splitSuffix = ""
for (let index = conversation.length - 1; index >= 0; index--) {
const next = total + Token.estimate(conversation[index])
if (next > tokens) {
const remaining = Math.max(0, tokens - total) * 4
if (remaining > 0) {
splitPrefix = conversation[index].slice(0, -remaining)
splitSuffix = conversation[index].slice(-remaining)
split = index + 1
}
break
}
if (next > tokens) break
total = next
split = index
}
return {
head: [...conversation.slice(0, split), splitPrefix].filter(Boolean).join("\n\n"),
recent: [splitSuffix, ...conversation.slice(split)].filter(Boolean).join("\n\n"),
head: conversation.slice(0, split).join("\n\n"),
recent: conversation.slice(split).join("\n\n"),
}
}
export const buildPrompt = (input: { readonly previousSummary?: string; readonly context: readonly string[] }) =>
[
input.previousSummary
? `Update the anchored summary below using the conversation history above.\nPreserve still-true details, remove stale details, and merge in the new facts.\n<previous-summary>\n${input.previousSummary}\n</previous-summary>`
: "Create a new anchored summary from the conversation history.",
export const buildPrompt = (input: { readonly previousSummary?: string; readonly context: readonly string[] }) => {
const conversation = `Here is the conversation so far:\n\n<conversation>\n${input.context.join("\n\n")}\n</conversation>`
if (!input.previousSummary)
return [
conversation,
"Create a new anchored summary from the conversation history in the <conversation> tags above so another coding agent can continue the work.",
SUMMARY_TEMPLATE,
].join("\n\n")
return [
conversation,
`Here is the summary of the conversation before the <conversation> above:\n\n<prior-summary>\n${input.previousSummary}\n</prior-summary>`,
SUMMARY_UPDATE_INSTRUCTIONS,
SUMMARY_TEMPLATE,
...input.context,
].join("\n\n")
}
export const make = (dependencies: Dependencies) => {
const config = settings(dependencies.config)
-3
View File
@@ -12,7 +12,6 @@ import { SessionMessage } from "./message"
import { SessionMessageUpdater } from "./message-updater"
import { SessionInput } from "./input"
import { WorkspaceV2 } from "../workspace"
import { SessionContextEpoch } from "./context-epoch"
import { MessageTable, PartTable, SessionInputTable, SessionMessageTable, SessionTable } from "./sql"
import type { DeepMutable } from "../schema"
@@ -253,7 +252,6 @@ const layer = Layer.effectDiscard(
.where(eq(SessionTable.id, event.data.sessionID))
.run()
.pipe(Effect.orDie)
yield* SessionContextEpoch.reset(db, event.data.sessionID)
}),
)
yield* events.project(SessionV1.Event.Deleted, (event) =>
@@ -449,7 +447,6 @@ const layer = Layer.effectDiscard(
.where(eq(SessionTable.id, event.data.sessionID))
.run()
.pipe(Effect.orDie)
yield* SessionContextEpoch.reset(db, event.data.sessionID)
}),
)
}),
+1 -1
View File
@@ -156,7 +156,7 @@ export const Info = Schema.Struct({
}),
tail_turns: Schema.optional(NonNegativeInt).annotate({
description:
"Number of recent user turns, including their following assistant/tool responses, to keep verbatim during compaction (default: 2)",
"Maximum number of recent user turns, including their following assistant/tool responses, to keep verbatim during compaction. By default retention is limited only by the preserved token budget.",
}),
preserve_recent_tokens: Schema.optional(NonNegativeInt).annotate({
description: "Maximum number of tokens from recent turns to preserve verbatim after compaction",
@@ -8,7 +8,6 @@ import { Effect, Layer } from "effect"
import { eq, inArray, sql } from "drizzle-orm"
import { DatabaseMigration } from "@opencode-ai/core/database/migration"
import { migrations } from "@opencode-ai/core/database/migration.gen"
import workspaceNameMigration from "@opencode-ai/core/database/migration/20260410174513_workspace-name"
import sessionUsageMigration from "@opencode-ai/core/database/migration/20260510033149_session_usage"
import normalizeStoragePathsMigration from "@opencode-ai/core/database/migration/20260601010001_normalize_storage_paths"
import sessionMessageProjectionOrderMigration from "@opencode-ai/core/database/migration/20260603040000_session_message_projection_order"
@@ -320,37 +319,6 @@ describe("DatabaseMigration", () => {
)
})
test("defaults missing workspace names during legacy workspace rebuild", async () => {
await run(
Effect.gen(function* () {
const db = yield* makeDb
yield* db.run(sql`
CREATE TABLE workspace (
id text PRIMARY KEY,
type text NOT NULL,
branch text,
directory text,
extra text,
project_id text NOT NULL
)
`)
yield* db.run(sql`
INSERT INTO workspace (id, type, branch, directory, extra, project_id)
VALUES ('wrk_legacy', 'remote', 'main', '/repo', '{}', 'proj_legacy')
`)
yield* DatabaseMigration.applyOnly(db, [workspaceNameMigration])
expect(yield* db.get(sql`SELECT id, name, directory, extra FROM workspace`)).toEqual({
id: "wrk_legacy",
name: "",
directory: "/repo",
extra: "{}",
})
}),
)
})
test("resets incompatible projected Session messages before adding sequence order", async () => {
await run(
Effect.gen(function* () {
+28
View File
@@ -0,0 +1,28 @@
import { createGroq } from "@ai-sdk/groq"
import { expect, test } from "bun:test"
test("Groq passes through unknown reasoning effort", async () => {
let body: Record<string, unknown> | undefined
const mockFetch = Object.assign(
async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
body = JSON.parse(String(init?.body))
return Response.json({
id: "response-1",
created: 0,
model: "openai/gpt-oss-120b",
object: "chat.completion",
choices: [{ index: 0, message: { role: "assistant", content: "Hello" }, finish_reason: "stop" }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
})
},
{ preconnect: fetch.preconnect },
)
const model = createGroq({ apiKey: "test", fetch: mockFetch })("openai/gpt-oss-120b")
await model.doGenerate({
prompt: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
providerOptions: { groq: { reasoningEffort: "custom" } },
})
expect(body?.reasoning_effort).toBe("custom")
})
@@ -27,6 +27,32 @@ test("Mistral sends promptCacheKey as prompt_cache_key", async () => {
expect(body?.prompt_cache_key).toBe("session-123")
})
test("Mistral passes through unknown reasoning effort", async () => {
let body: Record<string, unknown> | undefined
const mockFetch = Object.assign(
async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
body = JSON.parse(String(init?.body))
return Response.json({
id: "response-1",
created: 0,
model: "mistral-large-latest",
object: "chat.completion",
choices: [{ index: 0, message: { role: "assistant", content: "Hello" }, finish_reason: "stop" }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
})
},
{ preconnect: fetch.preconnect },
)
const model = createMistral({ apiKey: "test", fetch: mockFetch })("mistral-large-latest")
await model.doGenerate({
prompt: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
providerOptions: { mistral: { reasoningEffort: "custom" } },
})
expect(body?.reasoning_effort).toBe("custom")
})
test("Mistral round-trips native reasoning in assistant history", async () => {
let body: { messages?: unknown[] } | undefined
const mockFetch = Object.assign(
@@ -30,3 +30,56 @@ test("xAI Responses sends promptCacheKey as prompt_cache_key", async () => {
expect(body?.prompt_cache_key).toBe("session-123")
})
test("xAI Responses passes through xhigh reasoning effort", async () => {
let body: Record<string, unknown> | undefined
const mockFetch = Object.assign(
async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
body = JSON.parse(String(init?.body))
return Response.json({
id: "response-1",
created_at: 0,
model: "grok-4",
object: "response",
output: [],
usage: { input_tokens: 1, output_tokens: 0 },
status: "completed",
})
},
{ preconnect: fetch.preconnect },
)
const model = createXai({ apiKey: "test", fetch: mockFetch }).responses("grok-4")
await model.doGenerate({
prompt: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
providerOptions: { xai: { reasoningEffort: "xhigh" } },
})
expect(body?.reasoning).toEqual({ effort: "xhigh" })
})
test("xAI Chat passes through xhigh reasoning effort", async () => {
let body: Record<string, unknown> | undefined
const mockFetch = Object.assign(
async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
body = JSON.parse(String(init?.body))
return Response.json({
id: "chat-1",
created: 0,
model: "grok-4",
object: "chat.completion",
choices: [{ index: 0, message: { role: "assistant", content: "Hello" }, finish_reason: "stop" }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
})
},
{ preconnect: fetch.preconnect },
)
const model = createXai({ apiKey: "test", fetch: mockFetch }).chat("grok-4")
await model.doGenerate({
prompt: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
providerOptions: { xai: { reasoningEffort: "xhigh" } },
})
expect(body?.reasoning_effort).toBe("xhigh")
})
+20
View File
@@ -62,4 +62,24 @@ describe("Ripgrep", () => {
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
),
)
it.live("does not split surrogate pairs in oversized line previews", () =>
Effect.acquireUseRelease(
Effect.promise(() => tmpdir()),
(tmp) =>
Effect.gen(function* () {
yield* Effect.promise(() =>
fs.writeFile(path.join(tmp.path, "unicode.txt"), `needle${"x".repeat(1_993)}😀\n`),
)
const matches = yield* (yield* Ripgrep.Service).grep({
cwd: tmp.path,
pattern: "needle",
limit: 10,
})
expect(matches[0]?.text).toBe(`needle${"x".repeat(1_993)}...`)
}),
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
),
)
})
@@ -4,12 +4,32 @@ import { SessionCompaction } from "@opencode-ai/core/session/compaction"
test("compaction prompt preserves detailed work state and relevant files", () => {
const prompt = SessionCompaction.buildPrompt({ context: ["conversation history"] })
expect(prompt).toStartWith(
"Here is the conversation so far:\n\n<conversation>\nconversation history\n</conversation>",
)
expect(prompt.indexOf("</conversation>")).toBeLessThan(prompt.indexOf("Create a new anchored summary"))
expect(prompt).toContain("conversation history in the <conversation> tags above")
expect(prompt).toContain("## Work State\n### Completed")
expect(prompt).toContain("### Active")
expect(prompt).toContain("### Blocked")
expect(prompt).toContain("## Relevant Files")
})
test("compaction prompt gives update instructions for a prior summary", () => {
const prompt = SessionCompaction.buildPrompt({
context: ["new conversation"],
previousSummary: "existing summary",
})
expect(prompt.indexOf("<conversation>")).toBeLessThan(prompt.indexOf("<prior-summary>"))
expect(prompt.indexOf("</prior-summary>")).toBeLessThan(prompt.indexOf("The <prior-summary> summarizes"))
expect(prompt).toContain(
"Carry forward objectives, constraints, user directives, decisions, and parallel workstreams from the <prior-summary>",
)
expect(prompt).toContain('Move completed work from "Active" to "Completed".')
expect(prompt).toContain('Update "Objective" and "Next Move" to reflect the current work state.')
})
test("compaction describes tool media without embedding base64", () => {
const base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
const serialized = SessionCompaction.serializeToolContent([
+35 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { DateTime, Effect, Schema } from "effect"
import { asc, eq } from "drizzle-orm"
import { asc, eq, sql } from "drizzle-orm"
import { Database } from "@opencode-ai/core/database/database"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
@@ -22,6 +22,7 @@ import { SessionInput } from "@opencode-ai/core/session/input"
import { SessionInputTable, SessionMessageTable, SessionTable } from "@opencode-ai/core/session/sql"
import { testEffect } from "./lib/effect"
import { Snapshot } from "@opencode-ai/core/snapshot"
import { Location } from "@opencode-ai/core/location"
const it = testEffect(AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node, SessionProjector.node])))
const sessionsLayer = AppNodeBuilder.build(SessionV2.node, [[SessionExecution.node, SessionExecution.noopLayer]])
@@ -44,6 +45,39 @@ const assistantRow = (
}
describe("SessionProjector", () => {
it.effect("projects moved sessions without the transitional context epoch table", () =>
Effect.gen(function* () {
const { db } = yield* Database.Service
const events = yield* EventV2.Service
yield* db
.insert(ProjectTable)
.values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
.run()
yield* db
.insert(SessionTable)
.values({
id: sessionID,
project_id: Project.ID.global,
slug: "test",
directory: "/project",
title: "test",
version: "test",
})
.run()
yield* db.run(sql`DROP TABLE session_context_epoch`)
yield* events.publish(SessionEvent.Moved, {
sessionID,
timestamp: DateTime.makeUnsafe(1),
location: Location.Ref.make({ directory: AbsolutePath.make("/project/subdir") }),
})
expect(yield* db.select({ directory: SessionTable.directory }).from(SessionTable).get()).toEqual({
directory: "/project/subdir",
})
}),
)
it.effect("projects staged, cleared, and committed reverts", () =>
Effect.gen(function* () {
const db = (yield* Database.Service).db
+62 -8
View File
@@ -703,13 +703,6 @@ describe("SessionRunnerLLM", () => {
timestamp: DateTime.makeUnsafe(1),
location: Location.Ref.make({ directory: AbsolutePath.make("/moved") }),
})
expect(
yield* db
.select()
.from(SessionContextEpochTable)
.where(eq(SessionContextEpochTable.session_id, sessionID))
.get(),
).toBeUndefined()
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
@@ -1135,7 +1128,7 @@ describe("SessionRunnerLLM", () => {
expect(requests).toHaveLength(2)
expect(userTexts(requests[0])[0]).toContain(
"<previous-summary>\n## Objective\n- Preserve the task\n</previous-summary>",
"<prior-summary>\n## Objective\n- Preserve the task\n</prior-summary>",
)
expect(userTexts(requests[0])[0]).toContain("Recent exact request")
expect((yield* (yield* SessionStore.Service).context(sessionID))[0]).toMatchObject({
@@ -1145,6 +1138,67 @@ describe("SessionRunnerLLM", () => {
}),
)
it.effect("retains only complete serialized messages during compaction", () =>
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
const earlier = `EARLIER_BOUNDARY ${"a".repeat(3_000)} EARLIER_END`
const recent = `RECENT_BOUNDARY ${"b".repeat(3_000)} RECENT_END`
response = fragmentFixture("text", "text-earlier", ["Earlier answer"]).completeEvents
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: earlier }), resume: false })
yield* session.resume(sessionID)
currentModel = compactModel
requests.length = 0
responses = [
fragmentFixture("text", "text-summary", ["## Objective\n- Preserve the task"]).completeEvents,
fragmentFixture("text", "text-final", ["Continued"]).completeEvents,
]
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: recent }), resume: false })
yield* session.resume(sessionID)
expect(requests).toHaveLength(2)
const summary = userTexts(requests[0])[0]
const continuation = userTexts(requests[1])[0]
expect(summary.match(/EARLIER_BOUNDARY/g)).toHaveLength(1)
expect(summary).toContain(`EARLIER_BOUNDARY ${"a".repeat(3_000)} EARLIER_END`)
expect(summary).not.toContain("RECENT_BOUNDARY")
expect(continuation).not.toContain("EARLIER_BOUNDARY")
expect(continuation).not.toContain("EARLIER_END")
expect(continuation).toContain("<recent-context>\n[Assistant]: Earlier answer")
expect(continuation).toContain(`RECENT_BOUNDARY ${"b".repeat(3_000)} RECENT_END`)
}),
)
it.effect("summarizes an oversized newest message without retaining a fragment", () =>
Effect.gen(function* () {
yield* setup
const session = yield* SessionV2.Service
response = fragmentFixture("text", "text-earlier", ["Earlier answer"]).completeEvents
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Earlier question" }), resume: false })
yield* session.resume(sessionID)
const oversized = `OVERSIZED_BOUNDARY ${"x".repeat(4_500)} OVERSIZED_END`
currentModel = compactModel
requests.length = 0
responses = [
fragmentFixture("text", "text-summary", ["## Objective\n- Preserve the task"]).completeEvents,
fragmentFixture("text", "text-final", ["Continued"]).completeEvents,
]
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: oversized }), resume: false })
yield* session.resume(sessionID)
expect(requests).toHaveLength(2)
const summary = userTexts(requests[0])[0]
const continuation = userTexts(requests[1])[0]
expect(summary.match(/OVERSIZED_BOUNDARY/g)).toHaveLength(1)
expect(summary).toContain(oversized)
expect(continuation).not.toContain("OVERSIZED_BOUNDARY")
expect(continuation).not.toContain("OVERSIZED_END")
expect(continuation).toContain("<recent-context>\n\n</recent-context>")
}),
)
it.effect("forces one compaction and retries after provider context overflow", () =>
Effect.gen(function* () {
const session = yield* setupOverflowRecovery
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
"version": "1.18.16",
"version": "1.18.18",
"type": "module",
"license": "MIT",
"homepage": "https://opencode.ai",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.16",
"version": "1.18.18",
"name": "@opencode-ai/effect-drizzle-sqlite",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.16",
"version": "1.18.18",
"name": "@opencode-ai/effect-sqlite-node",
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.18.16",
"version": "1.18.18",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.18.16",
"version": "1.18.18",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.16",
"version": "1.18.18",
"name": "@opencode-ai/http-recorder",
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.16",
"version": "1.18.18",
"name": "@opencode-ai/llm",
"type": "module",
"license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.18.16",
"version": "1.18.18",
"name": "opencode",
"type": "module",
"license": "MIT",
@@ -111,7 +111,7 @@
"@types/ws": "8.18.1",
"@zip.js/zip.js": "2.7.62",
"ai": "catalog:",
"ai-gateway-provider": "3.1.2",
"ai-gateway-provider": "3.2.0",
"bonjour-service": "1.3.0",
"chokidar": "4.0.3",
"cross-spawn": "catalog:",
@@ -1,9 +1,5 @@
You are an anchored context summarization assistant for coding sessions.
Summarize only the conversation history you are given. The newest turns may be kept verbatim outside your summary, so focus on the older context that still matters for continuing the work.
If the prompt includes a <previous-summary> block, treat it as the current anchored summary. Update it with the new history by preserving still-true details, removing stale details, and merging in new facts.
You are a context summarization agent. You are given a conversation between a user and an agent. Your goal is to produce a structured summary matching the format specified so another coding agent can continue the work.
Always follow the exact output structure requested by the user prompt. Keep every section, preserve exact file paths and identifiers when known, and prefer terse bullets over paragraphs.
Do not answer the conversation itself. Do not mention that you are summarizing, compacting, or merging context. Respond in the same language as the conversation.
Do not continue the conversation. Do not respond to any questions in the conversation. Only output the structured summary in the exact format requested by the user prompt. Respond in the same language as the conversation.
+1 -1
View File
@@ -15,7 +15,7 @@ const dim = (value: string) => UI.Style.TEXT_DIM + value + UI.Style.TEXT_NORMAL
const activeSuffix = (isActive: boolean) => (isActive ? dim(" (active)") : "")
export const defaultConsoleUrl = "https://console.opencode.ai"
export const defaultConsoleUrl = "https://opencode.ai/console"
export const formatAccountLabel = (account: { email: string; url: string }, isActive: boolean) =>
`${account.email} ${dim(account.url)}${activeSuffix(isActive)}`
@@ -714,6 +714,7 @@ const layer = Layer.effect(
})
const list = Effect.fn("Workspace.list")(function* (project: Project.Info) {
if (!flags.experimentalWorkspaces) return []
return (yield* db
.select()
.from(WorkspaceTable)
@@ -851,6 +852,7 @@ const layer = Layer.effect(
})
const startWorkspaceSyncing = Effect.fn("Workspace.startWorkspaceSyncing")(function* (projectID: ProjectV2.ID) {
if (!flags.experimentalWorkspaces) return
const rows = yield* db
.selectDistinct({ workspace: WorkspaceTable })
.from(WorkspaceTable)
+152 -1
View File
@@ -1,6 +1,70 @@
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import type { Hooks, PluginInput } from "@opencode-ai/plugin"
import type { Provider } from "@opencode-ai/sdk/v2"
import { Schema } from "effect"
import { OAUTH_DUMMY_KEY } from "../auth"
const AZURE_COGNITIVE_SERVICES_SCOPE = "https://cognitiveservices.azure.com/.default"
const AZURE_FOUNDRY_SCOPE = "https://ai.azure.com/.default"
const AZURE_TOKEN_REFRESH_BUFFER = 60_000
const AzureCliToken = Schema.Struct({
accessToken: Schema.NonEmptyString,
expires_on: Schema.Number,
})
const decodeAzureCliToken = Schema.decodeUnknownPromise(AzureCliToken)
const decodeAzureAccounts = Schema.decodeUnknownPromise(
Schema.Array(
Schema.Struct({
name: Schema.NonEmptyString,
resourceGroup: Schema.NonEmptyString,
}),
),
)
const decodeAzureDeployments = Schema.decodeUnknownPromise(
Schema.Array(
Schema.Struct({
name: Schema.NonEmptyString,
properties: Schema.Struct({
model: Schema.Struct({
name: Schema.NonEmptyString,
}),
provisioningState: Schema.NonEmptyString,
}),
}),
),
)
type AzureCommand = {
quiet(): AzureCommand
json(): Promise<unknown>
}
type AzureShell = (strings: TemplateStringsArray, ...values: string[]) => AzureCommand
export async function AzureAuthPlugin(input: PluginInput): Promise<Hooks> {
return createAzureAuthHooks(input.$)
}
export function createAzureAuthHooks(
shell: AzureShell,
request: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> = fetch,
): Hooks {
const tokens = new Map<string, { token: string; expires: number }>()
async function token(scope: string) {
const cached = tokens.get(scope)
if (cached && cached.expires - Date.now() > AZURE_TOKEN_REFRESH_BUFFER) return cached.token
const result = await decodeAzureCliToken(
await shell`az account get-access-token --scope ${scope} --output json`.quiet().json(),
)
const token = { token: result.accessToken, expires: result.expires_on * 1000 }
tokens.set(scope, token)
return token.token
}
export async function AzureAuthPlugin(_input: PluginInput): Promise<Hooks> {
const prompts = []
if (!process.env.AZURE_RESOURCE_NAME) {
prompts.push({
@@ -12,15 +76,102 @@ export async function AzureAuthPlugin(_input: PluginInput): Promise<Hooks> {
}
return {
provider: {
id: "azure",
async models(provider, context) {
if (context.auth?.type !== "oauth") return provider.models
if (!context.auth.accountId) return {}
return discoverAzureModels(provider.models, context.auth.accountId, shell).catch(() => ({}))
},
},
auth: {
provider: "azure",
async loader(getAuth) {
if ((await getAuth()).type !== "oauth") return {}
return {
apiKey: OAUTH_DUMMY_KEY,
async fetch(input: RequestInfo | URL, init?: RequestInit) {
const headers = new Headers(input instanceof Request ? input.headers : undefined)
new Headers(init?.headers).forEach((value, key) => headers.set(key, value))
headers.delete("api-key")
headers.delete("x-api-key")
headers.set("authorization", `Bearer ${await token(scopeForRequest(input))}`)
headers.set("User-Agent", `opencode/${InstallationVersion}`)
return request(input, { ...init, headers })
},
}
},
methods: [
{
type: "api",
label: "API key",
prompts,
},
{
type: "oauth",
label: "Microsoft Entra ID (Azure CLI)",
prompts,
async authorize(inputs) {
return {
url: "",
instructions: "Sign in with `az login` before continuing.",
method: "auto",
callback: async () => {
const resourceName = inputs?.resourceName ?? process.env.AZURE_RESOURCE_NAME
if (!resourceName) throw new Error("Azure Resource Name is required")
await token(AZURE_COGNITIVE_SERVICES_SCOPE)
return {
type: "success",
access: OAUTH_DUMMY_KEY,
refresh: OAUTH_DUMMY_KEY,
expires: Date.now() + 365 * 24 * 60 * 60 * 1000,
accountId: resourceName,
}
},
}
},
},
],
},
}
}
async function discoverAzureModels(models: Provider["models"], resourceName: string, shell: AzureShell) {
const accounts = await decodeAzureAccounts(
await shell`az cognitiveservices account list --output json --only-show-errors`.quiet().json(),
)
const account = accounts.find((account) => account.name.toLowerCase() === resourceName.toLowerCase())
if (!account) return {}
const deployments = await decodeAzureDeployments(
await shell`az cognitiveservices account deployment list --name ${account.name} --resource-group ${account.resourceGroup} --output json --only-show-errors`
.quiet()
.json(),
)
const found = new Map<string, Provider["models"][string]>()
deployments.forEach((deployment) => {
if (deployment.properties.provisioningState !== "Succeeded") return
const modelID = Object.keys(models).find(
(modelID) => modelID.toLowerCase() === deployment.properties.model.name.toLowerCase(),
)
if (!modelID) return
found.set(modelID, {
...models[modelID],
api: {
...models[modelID].api,
id: deployment.name,
},
})
})
return Object.fromEntries(found)
}
function scopeForRequest(input: RequestInfo | URL) {
const url = new URL(input instanceof Request ? input.url : input)
if (url.hostname.endsWith(".services.ai.azure.com") && !url.pathname.startsWith("/models")) {
return AZURE_FOUNDRY_SCOPE
}
return AZURE_COGNITIVE_SERVICES_SCOPE
}
@@ -61,16 +61,5 @@ export async function CloudflareAIGatewayAuthPlugin(_input: PluginInput): Promis
},
],
},
"chat.params": async (input, output) => {
if (input.model.providerID !== "cloudflare-ai-gateway") return
// The unified gateway routes through @ai-sdk/openai-compatible, which
// always emits max_tokens. OpenAI reasoning models (gpt-5.x, o-series)
// reject that field and require max_completion_tokens instead, and the
// compatible SDK has no way to rename it. Drop the cap so OpenAI falls
// back to the model's default output budget.
if (!input.model.api.id.toLowerCase().startsWith("openai/")) return
if (!input.model.capabilities.reasoning) return
output.maxOutputTokens = undefined
},
}
}
@@ -88,6 +88,9 @@ function build(key: string, remote: SelectableItem, url: string, prev?: Model):
const image =
(remote.capabilities.supports.vision ?? false) ||
(remote.capabilities.limits.vision?.supported_media_types ?? []).some((item) => item.startsWith("image/"))
const pdf =
(remote.capabilities.supports.vision ?? false) &&
(remote.capabilities.limits.vision?.supported_media_types?.includes("application/pdf") ?? false)
const isMsgApi = remote.supported_endpoints?.includes("/v1/messages")
const endpoint: CopilotEndpoint | undefined = isMsgApi
@@ -127,7 +130,7 @@ function build(key: string, remote: SelectableItem, url: string, prev?: Model):
audio: false,
image,
video: false,
pdf: false,
pdf,
},
output: {
text: true,
+4 -9
View File
@@ -299,16 +299,11 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug
output: 0,
cache: { read: 0, write: 0 },
},
limit: model.id.includes("gpt-5.5")
? {
context: 400_000,
input: 272_000,
output: 128_000,
}
: model.id.includes("gpt-5.6")
limit:
model.id.includes("gpt-5.5") || model.id.includes("gpt-5.6")
? {
context: 500_000,
input: 372_000,
context: 400_000,
input: 272_000,
output: 128_000,
}
: model.limit,
+37 -5
View File
@@ -244,6 +244,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
return [
provider.options?.resourceName,
auth?.type === "api" ? auth.metadata?.resourceName : undefined,
auth?.type === "oauth" ? auth.accountId : undefined,
env["AZURE_RESOURCE_NAME"],
].find((name) => typeof name === "string" && name.trim() !== "")
})
@@ -800,9 +801,10 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
)
}
// Use official ai-gateway-provider package (v2.x for AI SDK v5 compatibility)
const { createAiGateway } = yield* Effect.promise(() => import("ai-gateway-provider"))
const { createUnified } = yield* Effect.promise(() => import("ai-gateway-provider/providers/unified"))
const { createOpenAI } = yield* Effect.promise(() => import("ai-gateway-provider/providers/openai"))
const { createAnthropic } = yield* Effect.promise(() => import("ai-gateway-provider/providers/anthropic"))
const metadata = iife(() => {
if (input.options?.metadata) return input.options.metadata
@@ -829,12 +831,23 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
apiKey: apiToken,
...(Object.values(opts).some((v) => v !== undefined) ? { options: opts } : {}),
})
const unified = createUnified({ apiKey: apiToken })
return {
autoload: true,
async getModel(_sdk: any, modelID: string, _options?: Record<string, any>) {
// Model IDs use Unified API format: provider/model (e.g., "anthropic/claude-sonnet-4-5")
// Model IDs use Unified API format: provider/model (e.g., "anthropic/claude-sonnet-4-5").
// OpenAI and Anthropic ride their native passthrough routes so agents get the Responses
// and Messages APIs; new OpenAI models reject tools+reasoning_effort on chat completions.
// The passthrough wrappers inject a CF_TEMP_TOKEN sentinel that the gateway strips before
// dispatch, so upstream billing stays on the gateway (Unified Billing / stored BYOK).
if (modelID.startsWith("openai/")) return aigateway(createOpenAI()(modelID.slice("openai/".length)))
if (modelID.startsWith("anthropic/")) return aigateway(createAnthropic()(modelID.slice("anthropic/".length)))
// Workers AI is the only first-party provider whose upstream is Cloudflare itself, so it is
// the only one that should receive the Cloudflare token as its upstream Authorization header.
// The Unified API addresses Workers AI both with the explicit "workers-ai/" prefix and as
// bare "@cf/..." ids. Third-party providers must not receive the token; they rely on the
// gateway's stored/BYOK keys instead.
const isWorkersAi = modelID.startsWith("workers-ai/") || modelID.startsWith("@cf/")
const unified = createUnified(isWorkersAi ? { apiKey: apiToken } : {})
return aigateway(unified(modelID))
},
options: {},
@@ -1209,6 +1222,17 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
return result
}
// Cloudflare AI Gateway routes OpenAI and Anthropic models through their native
// passthrough SDKs (Responses / Messages APIs). Resolving the native npm before
// variants are computed makes reasoning variants produce payloads the native
// SDKs understand (e.g. anthropic `effort` instead of compat `reasoningEffort`).
function cloudflareGatewayNpm(providerID: string, modelID: string) {
if (providerID !== "cloudflare-ai-gateway") return undefined
if (modelID.startsWith("openai/")) return "@ai-sdk/openai"
if (modelID.startsWith("anthropic/")) return "@ai-sdk/anthropic"
return undefined
}
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
const base: Model = {
id: ModelV2.ID.make(model.id),
@@ -1218,7 +1242,11 @@ function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model
api: {
id: model.id,
url: model.provider?.api ?? provider.api ?? "",
npm: model.provider?.npm ?? provider.npm ?? "@ai-sdk/openai-compatible",
npm:
cloudflareGatewayNpm(provider.id, model.id) ??
model.provider?.npm ??
provider.npm ??
"@ai-sdk/openai-compatible",
},
status: model.status ?? "active",
headers: {},
@@ -1440,6 +1468,9 @@ const layer = Layer.effect(
model.provider?.npm ??
provider.npm ??
existingModel?.api.npm ??
// Config-defined gateway models bypass fromModelsDevModel, so resolve the
// native passthrough npm here before falling back to the catalog default.
cloudflareGatewayNpm(providerID, apiID) ??
modelsDev[providerID]?.npm ??
"@ai-sdk/openai-compatible"
const name = iife(() => {
@@ -1619,6 +1650,7 @@ const layer = Layer.effect(
for (const [modelID, model] of Object.entries(provider.models)) {
model.api.id = model.api.id ?? model.id ?? modelID
if (
// These chat aliases are invalid for the special handling in the
// built-in providers below, but custom providers may support them.
@@ -84,6 +84,8 @@ function sdkKey(npm: string): string | undefined {
return "gateway"
case "@openrouter/ai-sdk-provider":
return "openrouter"
case "merge-gateway-ai-sdk-provider":
return "mergeGateway"
case "ai-gateway-provider":
// ai-gateway-provider/unified wraps createOpenAICompatible({ name: "Unified" }),
// and @ai-sdk/openai-compatible parses compatibleOptions from one of
@@ -551,6 +553,12 @@ export function topP(model: Provider.Model) {
if (["minimax-m2", "kimi-k2.5", "kimi-k2p5", "kimi-k2-5"].some((s) => id.includes(s))) {
return 0.95
}
if (
["deepseek-v4-flash-0731", "deepseek-v4-flash:0731"].some((name) => id.includes(name)) ||
(id.includes("deepseek-v4-flash") && (model.providerID === "deepseek" || model.providerID.startsWith("opencode")))
) {
return 0.95
}
return undefined
}
@@ -1766,6 +1774,7 @@ function reasoningEffort(model: Provider.Model, effort: string) {
case "@ai-sdk/togetherai":
case "venice-ai-sdk-provider":
case "ai-gateway-provider":
case "merge-gateway-ai-sdk-provider":
return { reasoningEffort: effort }
case "@ai-sdk/cohere":
case "@ai-sdk/perplexity":
+24 -17
View File
@@ -29,9 +29,8 @@ export const PRUNE_MINIMUM = 20_000
export const PRUNE_PROTECT = 40_000
const TOOL_OUTPUT_MAX_CHARS = 2_000
const PRUNE_PROTECTED_TOOLS = ["skill"]
const DEFAULT_TAIL_TURNS = 2
const MIN_PRESERVE_RECENT_TOKENS = 2_000
const MAX_PRESERVE_RECENT_TOKENS = 8_000
const MAX_PRESERVE_RECENT_TOKENS = 15_000
type Turn = {
start: number
end: number
@@ -226,27 +225,22 @@ const layer = Layer.effect(
cfg: ConfigV1.Info
model: Provider.Model
}) {
const limit = input.cfg.compaction?.tail_turns ?? DEFAULT_TAIL_TURNS
if (limit <= 0) return { head: input.messages, tail_start_id: undefined }
const limit = input.cfg.compaction?.tail_turns
if (limit !== undefined && limit <= 0) return { head: input.messages, tail_start_id: undefined }
const budget = preserveRecentBudget({ cfg: input.cfg, model: input.model })
const all = turns(input.messages)
if (!all.length) return { head: input.messages, tail_start_id: undefined }
const recent = all.slice(-limit)
const sizes = yield* Effect.forEach(
recent,
(turn) =>
estimate({
messages: input.messages.slice(turn.start, turn.end),
model: input.model,
}),
{ concurrency: 1 },
)
const recent = limit === undefined ? all : all.slice(-limit)
let total = 0
let keep: Tail | undefined
for (let i = recent.length - 1; i >= 0; i--) {
const turn = recent[i]!
const size = sizes[i]
// estimate lazily so cost stays proportional to the retained tail, not the whole session
const size = yield* estimate({
messages: input.messages.slice(turn.start, turn.end),
model: input.model,
})
if (total + size <= budget) {
total += size
keep = { start: turn.start, id: turn.id }
@@ -381,10 +375,20 @@ const layer = Layer.effect(
{ sessionID: input.sessionID },
{ context: [], prompt: undefined },
)
const nextPrompt = compacting.prompt ?? buildPrompt({ previousSummary, context: compacting.context })
const msgs = structuredClone(selected.head)
yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
const conversation = msgs.map(serialize).filter(Boolean).join("\n\n")
const nextPrompt =
compacting.prompt ??
[
buildPrompt({
previousSummary,
context: [conversation],
}),
...compacting.context,
]
.filter(Boolean)
.join("\n\n")
const ctx = yield* InstanceState.context
const msg: SessionV1.Assistant = {
id: MessageID.ascending(),
@@ -430,7 +434,10 @@ const layer = Layer.effect(
content: [
{
type: "text",
text: [nextPrompt, "The following is the conversation history:", conversation]
text: [
nextPrompt,
...(compacting.prompt ? ["The following is the conversation history:", conversation] : []),
]
.filter(Boolean)
.join("\n\n"),
},
+11 -3
View File
@@ -25,8 +25,10 @@ export type Retryable = {
export const RETRY_INITIAL_DELAY = 2000
export const RETRY_BACKOFF_FACTOR = 2
export const RETRY_JITTER_FACTOR = 0.25
export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds
export const RETRY_MAX_DELAY = 2_147_483_647 // max 32-bit signed integer for setTimeout
export const RETRY_MAX_RETRIES = 5
const RETRYABLE_MESSAGE_PATTERNS = [
/429|500|502|503|504|524/i,
@@ -41,7 +43,7 @@ function cap(ms: number) {
return Math.min(ms, RETRY_MAX_DELAY)
}
export function delay(attempt: number, error?: SessionV1.APIError) {
export function delay(attempt: number, error?: SessionV1.APIError, random = Math.random()) {
if (error) {
const headers = error.data.responseHeaders
if (headers) {
@@ -67,11 +69,16 @@ export function delay(attempt: number, error?: SessionV1.APIError) {
}
}
return cap(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1))
return cap(exponential(attempt, random))
}
}
return cap(Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS))
return cap(Math.min(exponential(attempt, random), RETRY_MAX_DELAY_NO_HEADERS))
}
function exponential(attempt: number, random: number) {
const base = RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1)
return Math.ceil(base + base * RETRY_JITTER_FACTOR * random)
}
export function retryable(error: Err, provider: string) {
@@ -182,6 +189,7 @@ export function policy(opts: {
const error = opts.parse(meta.input)
const retry = retryable(error, opts.provider)
if (!retry) return Cause.done(meta.attempt)
if (meta.attempt > RETRY_MAX_RETRIES) return Cause.done(meta.attempt)
return Effect.gen(function* () {
const wait = delay(meta.attempt, SessionV1.APIError.isInstance(error) ? error : undefined)
const now = yield* Clock.currentTimeMillis
+5 -1
View File
@@ -40,7 +40,11 @@ export function provider(model: Provider.Model) {
if (model.api.id.includes("gemini-")) return [PROMPT_GEMINI]
if (model.api.id.includes("claude")) return [PROMPT_ANTHROPIC]
if (model.api.id.toLowerCase().includes("trinity")) return [PROMPT_TRINITY]
if (model.api.id.toLowerCase().includes("kimi")) return [PROMPT_KIMI]
if (
model.api.id.toLowerCase().includes("kimi") ||
["kimi-for-coding", "moonshotai", "moonshotai-cn"].includes(model.providerID)
)
return [PROMPT_KIMI]
return [PROMPT_DEFAULT]
}
+6 -1
View File
@@ -56,7 +56,12 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission"
import { McpCatalog } from "@/mcp/catalog"
export function webSearchEnabled(providerID: ProviderV2.ID, flags = { exa: false, parallel: false }) {
return providerID === ProviderV2.ID.opencode || flags.exa || flags.parallel
return (
providerID === ProviderV2.ID.opencode ||
providerID === ProviderV2.ID.make("opencode-go") ||
flags.exa ||
flags.parallel
)
}
type TaskDef = Tool.InferDef<typeof TaskTool>
+2 -2
View File
@@ -4,8 +4,8 @@ import stripAnsi from "strip-ansi"
import { defaultConsoleUrl, formatAccountLabel, formatOrgLine } from "../../src/cli/cmd/account"
describe("console account display", () => {
test("uses console.opencode.ai as the default login URL", () => {
expect(defaultConsoleUrl).toBe("https://console.opencode.ai")
test("uses opencode.ai/console as the default login URL", () => {
expect(defaultConsoleUrl).toBe("https://opencode.ai/console")
})
test("includes the account url in account labels", () => {
@@ -8,6 +8,7 @@ import { Effect, Exit, Fiber, Layer, Schema } from "effect"
import { HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import { eq } from "drizzle-orm"
import { GlobalBus, type GlobalEvent } from "@/bus/global"
import { Project } from "@/project/project"
import { Database } from "@opencode-ai/core/database/database"
import { ProjectV2 } from "@opencode-ai/core/project"
import { ProjectTable } from "@opencode-ai/core/project/sql"
@@ -133,6 +134,9 @@ const startWorkspaceSyncingWithFlag = (projectID: ProjectV2.ID, experimentalWork
Workspace.use.startWorkspaceSyncing(projectID).pipe(Effect.provide(workspaceLayer(experimentalWorkspaces))),
)
const listWithFlag = (project: Project.Info, experimentalWorkspaces: boolean) =>
Effect.runPromise(Workspace.use.list(project).pipe(Effect.provide(workspaceLayer(experimentalWorkspaces))))
function captureGlobalEvents() {
const events: GlobalEvent[] = []
const handler = (event: GlobalEvent) => events.push(event)
@@ -417,6 +421,18 @@ describe("workspace CRUD", () => {
{ git: true },
)
it.instance(
"list is disabled by the experimental workspace flag",
() =>
Effect.gen(function* () {
const instance = yield* requireInstance
yield* insertWorkspace(workspaceInfo(instance.project.id, "manual"))
expect(yield* Effect.promise(() => listWithFlag(instance.project, false))).toEqual([])
}),
{ git: true },
)
it.instance(
"create configures, persists, creates, starts local sync, and passes environment",
() =>
+259
View File
@@ -0,0 +1,259 @@
import { afterEach, describe, expect, test } from "bun:test"
import type { Hooks } from "@opencode-ai/plugin"
import type { Auth, Provider } from "@opencode-ai/sdk/v2"
import { OAUTH_DUMMY_KEY } from "../../src/auth"
import { createAzureAuthHooks } from "../../src/plugin/azure"
const resourceName = process.env.AZURE_RESOURCE_NAME
afterEach(() => {
if (resourceName === undefined) delete process.env.AZURE_RESOURCE_NAME
else process.env.AZURE_RESOURCE_NAME = resourceName
})
const oauth: Auth = {
type: "oauth",
access: OAUTH_DUMMY_KEY,
refresh: OAUTH_DUMMY_KEY,
expires: Date.now() + 60 * 60 * 1000,
accountId: "test-resource",
}
const provider: Provider = {
id: "azure",
name: "Azure",
source: "custom",
env: [],
options: {},
models: {},
}
function oauthMethod(hooks: Hooks) {
const method = hooks.auth?.methods.find((method) => method.type === "oauth")
if (!method || method.type !== "oauth") throw new Error("Azure OAuth method is missing")
return method
}
function loader(hooks: Hooks) {
if (!hooks.auth?.loader) throw new Error("Azure auth loader is missing")
return hooks.auth.loader
}
function customFetch(options: Record<string, unknown>) {
const result = options["fetch"]
if (typeof result !== "function") throw new Error("Azure custom fetch is missing")
return async (input: RequestInfo | URL, init?: RequestInit) => {
const response: unknown = await Reflect.apply(result, undefined, [input, init])
if (!(response instanceof Response)) throw new Error("Azure custom fetch returned an invalid response")
return response
}
}
function models(...ids: string[]): Provider["models"] {
return Object.fromEntries(
ids.map((id) => [
id,
{
id,
providerID: "azure",
name: id,
family: "",
api: { id, url: "", npm: "@ai-sdk/azure" },
status: "active",
headers: {},
options: {},
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
limit: { context: 0, output: 0 },
capabilities: {
temperature: true,
reasoning: false,
attachment: false,
toolcall: true,
input: { text: true, audio: false, image: false, video: false, pdf: false },
output: { text: true, audio: false, image: false, video: false, pdf: false },
interleaved: false,
},
release_date: "",
variants: {},
},
]),
)
}
function azureShell(scopes: string[]) {
return (_strings: TemplateStringsArray, ...values: string[]) => {
const output = {
quiet: () => output,
json: async () => {
const scope = values[0]
scopes.push(scope)
return {
accessToken: `${scope}-token`,
expires_on: Math.floor((Date.now() + 60 * 60 * 1000) / 1000),
}
},
}
return output
}
}
function discoveryShell(accounts: unknown, deployments: unknown, commands: string[]) {
return (strings: TemplateStringsArray, ...values: string[]) => {
const command = String.raw(strings, ...values)
commands.push(command)
const output = {
quiet: () => output,
json: async () => (command.includes("deployment list") ? deployments : accounts),
}
return output
}
}
describe("plugin.azure", () => {
test("keeps the existing API-key method and adds Entra ID", () => {
delete process.env.AZURE_RESOURCE_NAME
const hooks = createAzureAuthHooks(azureShell([]))
expect(hooks.auth?.provider).toBe("azure")
expect(hooks.provider?.id).toBe("azure")
expect(hooks.auth?.methods.map((method) => [method.type, method.label])).toEqual([
["api", "API key"],
["oauth", "Microsoft Entra ID (Azure CLI)"],
])
expect(hooks.auth?.methods[0]).toEqual({
type: "api",
label: "API key",
prompts: [
{
type: "text",
key: "resourceName",
message: "Enter Azure Resource Name",
placeholder: "e.g. my-models",
},
],
})
expect(hooks.auth?.methods[1].prompts).toEqual(hooks.auth?.methods[0].prompts)
})
test("checks Azure CLI and stores the resource name", async () => {
const scopes: string[] = []
const hooks = createAzureAuthHooks(azureShell(scopes))
const authorization = await oauthMethod(hooks).authorize({ resourceName: "test-resource" })
if (authorization.method !== "auto") throw new Error("Unexpected Azure authorization method")
expect(await authorization.callback()).toMatchObject({
type: "success",
access: OAUTH_DUMMY_KEY,
refresh: OAUTH_DUMMY_KEY,
accountId: "test-resource",
})
expect(scopes).toEqual(["https://cognitiveservices.azure.com/.default"])
})
test("discovers deployed models through Azure CLI", async () => {
const commands: string[] = []
const hooks = createAzureAuthHooks(
discoveryShell(
[{ name: "test-resource", resourceGroup: "test-group" }],
[
{
name: "gpt-production",
properties: { model: { name: "gpt-5-mini" }, provisioningState: "Succeeded" },
},
{
name: "DeepSeek-V4-Flash",
properties: { model: { name: "DeepSeek-V4-Flash" }, provisioningState: "Succeeded" },
},
{
name: "phi-production",
properties: { model: { name: "Phi-4-mini-instruct" }, provisioningState: "Succeeded" },
},
{
name: "gpt-5-nano",
properties: { model: { name: "gpt-5-nano" }, provisioningState: "Creating" },
},
],
commands,
),
)
const list = hooks.provider?.models
if (!list) throw new Error("Azure provider model hook is missing")
const result = await list(
{
...provider,
models: models("gpt-5-mini", "deepseek-v4-flash", "phi-4-mini", "phi-4-mini-instruct", "gpt-5-nano"),
},
{ auth: oauth },
)
expect(Object.keys(result)).toEqual(["gpt-5-mini", "deepseek-v4-flash", "phi-4-mini-instruct"])
expect(result["gpt-5-mini"].api.id).toBe("gpt-production")
expect(result["deepseek-v4-flash"].api.id).toBe("DeepSeek-V4-Flash")
expect(result["phi-4-mini-instruct"].api.id).toBe("phi-production")
expect(commands).toEqual([
"az cognitiveservices account list --output json --only-show-errors",
"az cognitiveservices account deployment list --name test-resource --resource-group test-group --output json --only-show-errors",
])
})
test("keeps startup running when Azure discovery fails", async () => {
const hooks = createAzureAuthHooks(() => {
const output = {
quiet: () => output,
json: async () => {
throw new Error("Azure CLI failed")
},
}
return output
})
const list = hooks.provider?.models
if (!list) throw new Error("Azure provider model hook is missing")
expect(await list({ ...provider, models: models("gpt-5-mini") }, { auth: oauth })).toEqual({})
})
test("does not change API-key loading", async () => {
const scopes: string[] = []
const hooks = createAzureAuthHooks(azureShell(scopes))
const catalog = models("gpt-5-mini")
const list = hooks.provider?.models
if (!list) throw new Error("Azure provider model hook is missing")
expect(await loader(hooks)(async () => ({ type: "api", key: "test-key" }), provider)).toEqual({})
expect(await list({ ...provider, models: catalog }, { auth: { type: "api", key: "test-key" } })).toBe(catalog)
expect(scopes).toEqual([])
})
test("uses Azure CLI bearer tokens for Azure inference endpoints", async () => {
const scopes: string[] = []
const requests: Headers[] = []
const hooks = createAzureAuthHooks(azureShell(scopes), async (_input, init) => {
requests.push(new Headers(init?.headers))
return new Response(null, { status: 200 })
})
const options = await loader(hooks)(async () => oauth, provider)
const request = customFetch(options)
await request("https://test-resource.openai.azure.com/openai/v1/responses", {
headers: { "api-key": OAUTH_DUMMY_KEY, "x-keep": "yes" },
})
await request("https://test-resource.services.ai.azure.com/models/chat/completions", {
headers: { Authorization: `Bearer ${OAUTH_DUMMY_KEY}` },
})
await request("https://test-resource.services.ai.azure.com/anthropic/v1/messages", {
headers: { "x-api-key": OAUTH_DUMMY_KEY },
})
expect(scopes).toEqual(["https://cognitiveservices.azure.com/.default", "https://ai.azure.com/.default"])
expect(requests.map((headers) => headers.get("authorization"))).toEqual([
"Bearer https://cognitiveservices.azure.com/.default-token",
"Bearer https://cognitiveservices.azure.com/.default-token",
"Bearer https://ai.azure.com/.default-token",
])
expect(requests[0].get("api-key")).toBeNull()
expect(requests[0].get("x-keep")).toBe("yes")
expect(requests[2].get("x-api-key")).toBeNull()
expect(requests.every((headers) => headers.get("user-agent")?.startsWith("opencode/"))).toBe(true)
})
})
@@ -13,56 +13,13 @@ const pluginInput = {
$: {} as never,
}
function makeHookInput(overrides: { providerID?: string; apiId?: string; reasoning?: boolean }) {
return {
sessionID: "s",
agent: "a",
provider: {} as never,
message: {} as never,
model: {
providerID: overrides.providerID ?? "cloudflare-ai-gateway",
api: { id: overrides.apiId ?? "openai/gpt-5.2-codex", url: "", npm: "ai-gateway-provider" },
capabilities: {
reasoning: overrides.reasoning ?? true,
temperature: false,
attachment: true,
toolcall: true,
input: { text: true, audio: false, image: false, video: false, pdf: false },
output: { text: true, audio: false, image: false, video: false, pdf: false },
interleaved: false,
},
} as never,
}
}
function makeHookOutput() {
return { temperature: 0, topP: 1, topK: 0, maxOutputTokens: 32_000 as number | undefined, options: {} }
}
test("omits maxOutputTokens for openai reasoning models on cloudflare-ai-gateway", async () => {
test("registers the cloudflare-ai-gateway auth method", async () => {
const hooks = await CloudflareAIGatewayAuthPlugin(pluginInput)
const out = makeHookOutput()
await hooks["chat.params"]!(makeHookInput({ apiId: "openai/gpt-5.2-codex", reasoning: true }), out)
expect(out.maxOutputTokens).toBeUndefined()
expect(hooks.auth?.provider).toBe("cloudflare-ai-gateway")
expect(hooks.auth?.methods).toHaveLength(1)
})
test("keeps maxOutputTokens for openai non-reasoning models", async () => {
test("no longer drops maxOutputTokens; OpenAI models ride the Responses API passthrough", async () => {
const hooks = await CloudflareAIGatewayAuthPlugin(pluginInput)
const out = makeHookOutput()
await hooks["chat.params"]!(makeHookInput({ apiId: "openai/gpt-4-turbo", reasoning: false }), out)
expect(out.maxOutputTokens).toBe(32_000)
})
test("keeps maxOutputTokens for non-openai reasoning models on cloudflare-ai-gateway", async () => {
const hooks = await CloudflareAIGatewayAuthPlugin(pluginInput)
const out = makeHookOutput()
await hooks["chat.params"]!(makeHookInput({ apiId: "anthropic/claude-sonnet-4-5", reasoning: true }), out)
expect(out.maxOutputTokens).toBe(32_000)
})
test("ignores non-cloudflare-ai-gateway providers", async () => {
const hooks = await CloudflareAIGatewayAuthPlugin(pluginInput)
const out = makeHookOutput()
await hooks["chat.params"]!(makeHookInput({ providerID: "openai", apiId: "gpt-5.2-codex", reasoning: true }), out)
expect(out.maxOutputTokens).toBe(32_000)
expect(hooks["chat.params"]).toBeUndefined()
})
+3 -3
View File
@@ -181,9 +181,9 @@ describe("plugin.codex", () => {
expect(models["gpt-5.4"]?.limit).toEqual(limit)
expect(models["gpt-5.5"]?.limit).toEqual({ context: 400_000, input: 272_000, output: 128_000 })
expect(models["gpt-5.6-sol"]?.limit).toEqual({ context: 500_000, input: 372_000, output: 128_000 })
expect(models["gpt-5.6-terra"]?.limit).toEqual({ context: 500_000, input: 372_000, output: 128_000 })
expect(models["gpt-5.6-luna"]?.limit).toEqual({ context: 500_000, input: 372_000, output: 128_000 })
expect(models["gpt-5.6-sol"]?.limit).toEqual({ context: 400_000, input: 272_000, output: 128_000 })
expect(models["gpt-5.6-terra"]?.limit).toEqual({ context: 400_000, input: 272_000, output: 128_000 })
expect(models["gpt-5.6-luna"]?.limit).toEqual({ context: 400_000, input: 272_000, output: 128_000 })
expect(models["gpt-5.4-pro"]).toBeUndefined()
expect(models["gpt-5.7-pro"]).toBeDefined()
expect(models["gpt-5.6-sol-high"]).toBeDefined()
@@ -187,6 +187,74 @@ test("converts Copilot AIC token prices to USD per million tokens", async () =>
expect(models["ignored-non-chat-record"]).toBeUndefined()
})
test("detects PDF input support when vision and media type are advertised", async () => {
globalThis.fetch = mock(() =>
Promise.resolve(
new Response(
JSON.stringify({
data: [
{
model_picker_enabled: true,
id: "pdf-model",
name: "PDF Model",
version: "pdf-model-2026-06-01",
capabilities: {
family: "pdf-model",
limits: {
max_context_window_tokens: 128000,
max_output_tokens: 16384,
max_prompt_tokens: 128000,
vision: {
max_prompt_image_size: 10000000,
max_prompt_images: 10,
supported_media_types: ["application/pdf"],
},
},
supports: {
streaming: true,
vision: true,
tool_calls: true,
},
},
},
{
model_picker_enabled: true,
id: "vision-only-model",
name: "Vision Only Model",
version: "vision-only-model-2026-06-01",
capabilities: {
family: "vision-only-model",
limits: {
max_context_window_tokens: 128000,
max_output_tokens: 16384,
max_prompt_tokens: 128000,
vision: {
max_prompt_image_size: 10000000,
max_prompt_images: 10,
supported_media_types: ["image/png"],
},
},
supports: {
streaming: true,
vision: true,
tool_calls: true,
},
},
},
],
}),
{ status: 200 },
),
),
) as unknown as typeof fetch
const models = (await CopilotModels.get("https://api.githubcopilot.com")).models
const model = models["pdf-model"]
expect(model.capabilities.input.pdf).toBe(true)
expect(models["vision-only-model"].capabilities.input.pdf).toBe(false)
})
test("uses zero cost when Copilot reports a zero billing batch size", async () => {
globalThis.fetch = mock(() =>
Promise.resolve(
@@ -1,22 +1,26 @@
// End-to-end regression test for opencode#24432.
// End-to-end regression tests for opencode#24432 and opencode#32051/#32052.
//
// Routes through the actual ai-gateway-provider + @ai-sdk/openai-compatible
// chain that provider.ts:811 builds at runtime, with only the network boundary
// stubbed. Asserts that `reasoning_effort` (and other provider options the
// transform emits) actually land in the body Cloudflare AI Gateway forwards
// upstream, which is the only place the bug was observable.
// Routes through the actual ai-gateway-provider chain that provider.ts builds at
// runtime, with only the network boundary stubbed:
// - openai/* -> native OpenAI passthrough (Responses API)
// - anthropic/* -> native Anthropic passthrough (Messages API)
// - everything else -> unified /compat (openai-compatible chat completions)
// Asserts what actually lands in the envelope body Cloudflare AI Gateway
// forwards upstream, which is the only place these bugs were observable.
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
import type { JSONValue } from "ai"
import { generateText } from "ai"
import { createAiGateway } from "ai-gateway-provider"
import { createUnified } from "ai-gateway-provider/providers/unified"
import { createOpenAI } from "ai-gateway-provider/providers/openai"
import { createAnthropic } from "ai-gateway-provider/providers/anthropic"
import { ProviderTransform } from "@/provider/transform"
import type * as Provider from "@/provider/provider"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
type Captured = { url: string; outerBody: unknown }
type Captured = { url: string; outerBody: unknown; headers: Record<string, string> }
type ProviderOptions = Record<string, Record<string, JSONValue>>
const realFetch = globalThis.fetch
@@ -26,24 +30,76 @@ function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
}
// The gateway returns the upstream provider's response body verbatim, so the
// mock must answer in the wire format of the step's target provider.
function upstreamResponseBody(provider: string | undefined) {
if (provider === "openai")
return {
id: "resp_test",
object: "response",
created_at: 0,
model: "gpt-5.4",
status: "completed",
error: null,
incomplete_details: null,
output: [
{
type: "message",
role: "assistant",
id: "msg_1",
status: "completed",
content: [{ type: "output_text", text: "ok", annotations: [] }],
},
],
usage: {
input_tokens: 1,
input_tokens_details: { cached_tokens: 0 },
output_tokens: 1,
output_tokens_details: { reasoning_tokens: 0 },
total_tokens: 2,
},
}
if (provider === "anthropic")
return {
id: "msg_test",
type: "message",
role: "assistant",
model: "claude-sonnet-4-6",
content: [{ type: "text", text: "ok" }],
stop_reason: "end_turn",
stop_sequence: null,
usage: { input_tokens: 1, output_tokens: 1 },
}
return {
id: "chatcmpl-test",
object: "chat.completion",
created: 0,
model: "test",
choices: [{ index: 0, message: { role: "assistant", content: "ok" }, finish_reason: "stop" }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}
}
beforeEach(() => {
captured = null
const handle = async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]): Promise<Response> => {
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url
if (url.startsWith("https://gateway.ai.cloudflare.com/")) {
const bodyText = typeof init?.body === "string" ? init.body : ""
captured = { url, outerBody: bodyText ? JSON.parse(bodyText) : null }
return new Response(
JSON.stringify({
id: "chatcmpl-test",
object: "chat.completion",
created: 0,
model: "openai/gpt-5.4",
choices: [{ index: 0, message: { role: "assistant", content: "ok" }, finish_reason: "stop" }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
)
const outerBody = bodyText ? JSON.parse(bodyText) : null
captured = {
url,
outerBody,
headers: Object.fromEntries(new Headers(init?.headers).entries()),
}
const provider =
Array.isArray(outerBody) && isRecord(outerBody[0]) && typeof outerBody[0].provider === "string"
? outerBody[0].provider
: undefined
return new Response(JSON.stringify(upstreamResponseBody(provider)), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}
return realFetch(input, init)
}
@@ -56,11 +112,19 @@ afterEach(() => {
globalThis.fetch = realFetch
})
// Mirrors the runtime npm rewrite in provider.ts: openai/anthropic models carry
// their native SDK package so transforms key provider options correctly.
const cfNpm = (apiId: string) => {
if (apiId.startsWith("openai/")) return "@ai-sdk/openai"
if (apiId.startsWith("anthropic/")) return "@ai-sdk/anthropic"
return "ai-gateway-provider"
}
const cfModel = (apiId: string, releaseDate = "2026-03-05"): Provider.Model => ({
id: ModelV2.ID.make(`cloudflare-ai-gateway/${apiId}`),
providerID: ProviderV2.ID.make("cloudflare-ai-gateway"),
name: apiId,
api: { id: apiId, url: "https://gateway.ai.cloudflare.com/v1/compat", npm: "ai-gateway-provider" },
api: { id: apiId, url: "https://gateway.ai.cloudflare.com/v1/compat", npm: cfNpm(apiId) },
capabilities: {
reasoning: true,
temperature: false,
@@ -80,53 +144,165 @@ const cfModel = (apiId: string, releaseDate = "2026-03-05"): Provider.Model => (
// ai-gateway-provider sends an array of step descriptors; each entry's `query`
// is the body forwarded to the upstream provider.
function extractUpstreamQuery(body: unknown): Record<string, unknown> | undefined {
function firstStep(body: unknown): Record<string, unknown> | undefined {
if (!Array.isArray(body) || body.length === 0) return undefined
const first = body[0]
if (!isRecord(first)) return undefined
const query = first.query
return isRecord(first) ? first : undefined
}
function extractUpstreamQuery(body: unknown): Record<string, unknown> | undefined {
const query = firstStep(body)?.query
return isRecord(query) ? query : undefined
}
async function callThroughGateway(apiId: string, providerOptions: ProviderOptions) {
const aigateway = createAiGateway({ accountId: "test", gateway: "test", apiKey: "test" })
const unified = createUnified()
await generateText({ model: aigateway(unified(apiId)), prompt: "hi", providerOptions })
// Each step descriptor also carries the `headers` forwarded to the upstream provider.
function extractUpstreamHeaders(body: unknown): Record<string, unknown> | undefined {
const headers = firstStep(body)?.headers
return isRecord(headers) ? headers : undefined
}
// Mirrors the runtime routing in provider.ts getModel.
function gatewayModel(apiId: string, gatewayToken = "test") {
const aigateway = createAiGateway({ accountId: "test", gateway: "test", apiKey: gatewayToken })
if (apiId.startsWith("openai/")) return aigateway(createOpenAI()(apiId.slice("openai/".length)))
if (apiId.startsWith("anthropic/")) return aigateway(createAnthropic()(apiId.slice("anthropic/".length)))
const isWorkersAi = apiId.startsWith("workers-ai/") || apiId.startsWith("@cf/")
const unified = createUnified(isWorkersAi ? { apiKey: gatewayToken } : {})
return aigateway(unified(apiId))
}
async function callThroughGateway(apiId: string, providerOptions: ProviderOptions, gatewayToken = "test") {
await generateText({ model: gatewayModel(apiId, gatewayToken), prompt: "hi", providerOptions })
return extractUpstreamQuery(captured?.outerBody)
}
describe("cf-ai-gateway routing", () => {
test("openai/* rides the native OpenAI passthrough on the Responses API", async () => {
await callThroughGateway("openai/gpt-5.4", {})
const step = firstStep(captured?.outerBody)
expect(step?.provider).toBe("openai")
expect(step?.endpoint).toBe("v1/responses")
const upstream = extractUpstreamQuery(captured?.outerBody)
expect(upstream?.model).toBe("gpt-5.4")
})
test("anthropic/* rides the native Anthropic passthrough on the Messages API", async () => {
await callThroughGateway("anthropic/claude-sonnet-4-6", {})
const step = firstStep(captured?.outerBody)
expect(step?.provider).toBe("anthropic")
expect(step?.endpoint).toBe("v1/messages")
const upstream = extractUpstreamQuery(captured?.outerBody)
expect(upstream?.model).toBe("claude-sonnet-4-6")
})
test("workers-ai models stay on the unified /compat route", async () => {
await callThroughGateway("workers-ai/@cf/moonshotai/kimi-k2.6", {})
const step = firstStep(captured?.outerBody)
expect(step?.provider).toBe("compat")
expect(step?.endpoint).toBe("chat/completions")
const upstream = extractUpstreamQuery(captured?.outerBody)
expect(upstream?.model).toBe("workers-ai/@cf/moonshotai/kimi-k2.6")
})
})
describe("cf-ai-gateway end-to-end (regression: #24432)", () => {
test("ProviderTransform.providerOptions output puts reasoning_effort on the wire", async () => {
// The full chain the runtime exercises:
// transform.providerOptions() -> openaiCompatible key
// -> @ai-sdk/openai-compatible reads it as compatibleOptions
// -> emits body.reasoning_effort
test("ProviderTransform.providerOptions output puts reasoning effort on the Responses wire", async () => {
// The full chain the runtime exercises for OpenAI models:
// transform.providerOptions() -> "openai" key (npm rewritten to @ai-sdk/openai)
// -> OpenAIResponsesLanguageModel emits body.reasoning.effort
// -> ai-gateway-provider wraps the body and forwards to gateway.ai.cloudflare.com
const opts = ProviderTransform.providerOptions(cfModel("openai/gpt-5.4"), { reasoningEffort: "xhigh" })
expect(opts).toEqual({ openaiCompatible: { reasoningEffort: "xhigh" } })
expect(Object.keys(opts)).toEqual(["openai"])
expect(opts.openai.reasoningEffort).toBe("xhigh")
const upstream = await callThroughGateway("openai/gpt-5.4", opts)
expect(upstream?.reasoning_effort).toBe("xhigh")
expect((upstream?.reasoning as Record<string, unknown> | undefined)?.effort).toBe("xhigh")
})
test("variants() output for openai/gpt-5.4 lands xhigh on the wire", async () => {
// The other half of the bug: workflow `variant: xhigh` flows through variants()
// and must reach the wire. variants() returns the providerOptions payload
// unwrapped; providerOptions() wraps it under the SDK key.
// fromModelsDevModel resolves the native npm before computing variants, so
// OpenAI models get full Responses-flavored payloads (summary + encrypted
// reasoning include for stateless multi-turn reasoning).
const variants = ProviderTransform.variants(cfModel("openai/gpt-5.4"))
expect(variants.xhigh).toEqual({ reasoningEffort: "xhigh" })
expect(variants.xhigh).toEqual({
reasoningEffort: "xhigh",
reasoningSummary: "auto",
include: ["reasoning.encrypted_content"],
})
const opts = ProviderTransform.providerOptions(cfModel("openai/gpt-5.4"), variants.xhigh)
const upstream = await callThroughGateway("openai/gpt-5.4", opts)
expect(upstream?.reasoning_effort).toBe("xhigh")
const reasoning = upstream?.reasoning as Record<string, unknown> | undefined
expect(reasoning?.effort).toBe("xhigh")
expect(reasoning?.summary).toBe("auto")
})
test("reasoning effort variants for anthropic models land as native adaptive thinking", async () => {
// Mirrors the runtime catalog path: models.dev reasoning_options -> reasoningVariants
// computed on the native @ai-sdk/anthropic npm -> adaptive thinking + output_config.effort.
const model = cfModel("anthropic/claude-sonnet-4-6")
const variants = ProviderTransform.reasoningVariants(
{ reasoning_options: [{ type: "effort", values: ["low", "medium", "high"] }] } as never,
model,
)
expect(variants?.high).toMatchObject({ effort: "high" })
const opts = ProviderTransform.providerOptions(model, variants!.high)
expect(Object.keys(opts)).toEqual(["anthropic"])
const upstream = await callThroughGateway("anthropic/claude-sonnet-4-6", opts)
expect((upstream?.thinking as Record<string, unknown> | undefined)?.type).toBe("adaptive")
expect((upstream?.output_config as Record<string, unknown> | undefined)?.effort).toBe("high")
})
test("reasoning_effort still reaches the /compat wire for workers-ai models", async () => {
const model = cfModel("workers-ai/@cf/moonshotai/kimi-k2.6")
const opts = ProviderTransform.providerOptions(model, { reasoningEffort: "high" })
expect(opts).toEqual({ openaiCompatible: { reasoningEffort: "high" } })
const upstream = await callThroughGateway("workers-ai/@cf/moonshotai/kimi-k2.6", opts)
expect(upstream?.reasoning_effort).toBe("high")
})
test("legacy buggy key 'cloudflare-ai-gateway' does NOT reach the wire (proves the bug)", async () => {
// Sanity: confirms the bug class. If a future change accidentally restores
// providerID-keyed providerOptions, this test fails before users notice.
const upstream = await callThroughGateway("openai/gpt-5.4", {
const upstream = await callThroughGateway("workers-ai/@cf/moonshotai/kimi-k2.6", {
"cloudflare-ai-gateway": { reasoningEffort: "high" },
})
expect(upstream?.reasoning_effort).toBeUndefined()
})
})
describe("cf-ai-gateway token scoping (regression: #32051/#32052)", () => {
test("openai passthrough does NOT forward the Cloudflare token upstream", async () => {
await callThroughGateway("openai/gpt-5.4", {}, "cf-gateway-secret")
expect(captured?.headers["cf-aig-authorization"]).toBe("Bearer cf-gateway-secret")
// Security invariant: the Cloudflare token must never become the upstream provider's Authorization.
expect(extractUpstreamHeaders(captured?.outerBody)?.["authorization"]).toBeUndefined()
expect(JSON.stringify(captured?.outerBody)).not.toContain("cf-gateway-secret")
})
test("anthropic passthrough does NOT forward the Cloudflare token upstream", async () => {
await callThroughGateway("anthropic/claude-sonnet-4-6", {}, "cf-gateway-secret")
expect(captured?.headers["cf-aig-authorization"]).toBe("Bearer cf-gateway-secret")
expect(extractUpstreamHeaders(captured?.outerBody)?.["x-api-key"]).toBeUndefined()
expect(JSON.stringify(captured?.outerBody)).not.toContain("cf-gateway-secret")
})
test("workers-ai models DO forward the Cloudflare token upstream", async () => {
await callThroughGateway("workers-ai/@cf/google/gemma-4-26b-a4b-it", {}, "cf-gateway-secret")
expect(captured?.headers["cf-aig-authorization"]).toBe("Bearer cf-gateway-secret")
expect(extractUpstreamHeaders(captured?.outerBody)?.["authorization"]).toBe("Bearer cf-gateway-secret")
})
test("bare @cf/ Workers AI models DO forward the Cloudflare token upstream", async () => {
await callThroughGateway("@cf/meta/llama-3.1-8b-instruct", {}, "cf-gateway-secret")
expect(captured?.headers["cf-aig-authorization"]).toBe("Bearer cf-gateway-secret")
expect(extractUpstreamHeaders(captured?.outerBody)?.["authorization"]).toBe("Bearer cf-gateway-secret")
})
})
@@ -1548,6 +1548,33 @@ test("models.dev reasoning options replace generated variants and unsupported to
expect(models["gemini-3-pro-fast"].variants).toEqual(models.override.variants)
})
test("MERGE Gateway exposes declared effort variants without model-specific handling", () => {
const provider = {
id: "merge-gateway",
name: "MERGE Gateway",
env: ["MERGE_GATEWAY_API_KEY"],
npm: "merge-gateway-ai-sdk-provider",
models: {
"openai/gpt-5.6-sol": {
id: "openai/gpt-5.6-sol",
name: "GPT-5.6 Sol",
reasoning: true,
reasoning_options: [{ type: "effort", values: ["none", "low", "medium", "high", "xhigh", "max"] }],
limit: { context: 128_000, output: 64_000 },
},
},
} as unknown as ModelsDev.Provider
expect(Provider.fromModelsDevProvider(provider).models["openai/gpt-5.6-sol"].variants).toEqual({
none: { reasoningEffort: "none" },
low: { reasoningEffort: "low" },
medium: { reasoningEffort: "medium" },
high: { reasoningEffort: "high" },
xhigh: { reasoningEffort: "xhigh" },
max: { reasoningEffort: "max" },
})
})
test("public provider info omits invalid models", () => {
const provider = Provider.fromModelsDevProvider({
id: "test",
@@ -3272,6 +3272,37 @@ describe("ProviderTransform sampling defaults - Gemini", () => {
})
})
describe("ProviderTransform sampling defaults - DeepSeek", () => {
const model = (providerID: string, id: string) =>
({
id: `${providerID}/${id}`,
providerID,
api: { id },
}) as any
test.each([
["deepseek", "deepseek-v4-flash"],
["opencode", "deepseek-v4-flash"],
["opencode-go", "deepseek-v4-flash"],
["openrouter", "deepseek/deepseek-v4-flash-0731"],
["ollama-cloud", "deepseek-v4-flash:0731"],
])("defaults top_p for %s/%s", (providerID, id) => {
expect(ProviderTransform.temperature(model(providerID, id))).toBeUndefined()
expect(ProviderTransform.topP(model(providerID, id))).toBe(0.95)
expect(ProviderTransform.topK(model(providerID, id))).toBeUndefined()
})
test.each([
["openrouter", "deepseek/deepseek-v4-flash"],
["vercel", "deepseek/deepseek-v4-flash"],
["custom", "deepseek-ai/DeepSeek-V4-Flash"],
])("preserves legacy defaults for %s/%s", (providerID, id) => {
expect(ProviderTransform.temperature(model(providerID, id))).toBeUndefined()
expect(ProviderTransform.topP(model(providerID, id))).toBeUndefined()
expect(ProviderTransform.topK(model(providerID, id))).toBeUndefined()
})
})
describe("ProviderTransform.reasoningVariants", () => {
const model = (reasoning_options: ModelsDev.Model["reasoning_options"]) => ({ reasoning_options }) as ModelsDev.Model
const target = (npm: string, id = "test-model") =>
@@ -3339,6 +3370,7 @@ describe("ProviderTransform.reasoningVariants", () => {
["@ai-sdk/togetherai", { reasoningEffort: "high" }],
["venice-ai-sdk-provider", { reasoningEffort: "high" }],
["ai-gateway-provider", { reasoningEffort: "high" }],
["merge-gateway-ai-sdk-provider", { reasoningEffort: "high" }],
["@ai-sdk/amazon-bedrock", { reasoningConfig: { type: "enabled", maxReasoningEffort: "high" } }],
])("converts effort for %s", (npm, expected, ...args) => {
const id = args[0] as string | undefined
@@ -5524,6 +5556,25 @@ describe("ProviderTransform.providerOptions - ai-gateway-provider", () => {
})
})
describe("ProviderTransform.providerOptions - merge-gateway-ai-sdk-provider", () => {
const model = {
id: "merge-gateway/openai/gpt-5.6-sol",
providerID: "merge-gateway",
api: {
id: "openai/gpt-5.6-sol",
url: "https://api-gateway.merge.dev/v1/ai-sdk",
npm: "merge-gateway-ai-sdk-provider",
},
capabilities: { reasoning: true },
} as any
test("routes normalized effort under the adapter's mergeGateway key", () => {
expect(ProviderTransform.providerOptions(model, { reasoningEffort: "high" })).toEqual({
mergeGateway: { reasoningEffort: "high" },
})
})
})
describe("ProviderTransform.options - kimi family adaptive thinking", () => {
const createModel = (overrides: Record<string, any> = {}) =>
({
@@ -365,6 +365,20 @@ function autocontinue(enabled: boolean) {
})
}
function compactionContext(context: string) {
return Layer.mock(Plugin.Service)({
trigger: <Name extends string, Input, Output>(name: Name, _input: Input, output: Output) => {
if (name !== "experimental.session.compacting") return Effect.succeed(output)
return Effect.sync(() => {
;(output as { context: string[] }).context.push(context)
return output
})
},
list: () => Effect.succeed([]),
init: () => Effect.void,
})
}
describe("session.compaction.isOverflow", () => {
it.live(
"returns true when token count exceeds usable context",
@@ -1389,11 +1403,21 @@ describe("session.compaction.process", () => {
const captured = JSON.stringify(messages)
expect(messages).toHaveLength(1)
expect(messages[0]?.role).toBe("user")
expect(captured).toContain("Here is the conversation so far:")
expect(captured).toContain("<conversation>")
expect(captured.indexOf("[User]: older context")).toBeLessThan(
captured.indexOf("Create a new anchored summary"),
)
expect(captured).toContain("[User]: older context")
expect(captured).not.toContain("keep this turn")
expect(captured).not.toContain("and this one too")
expect(captured).not.toContain("What did we do so far?")
}).pipe(withCompaction({ llm: stub.llmLayer }))
}).pipe(
withCompaction({
llm: stub.llmLayer,
config: cfg({ tail_turns: 2, preserve_recent_tokens: 10_000 }),
}),
)
},
{ git: true },
)
@@ -1430,9 +1454,11 @@ describe("session.compaction.process", () => {
expect(parent).toBeTruthy()
yield* SessionCompaction.use.process({ parentID: parent!, messages: msgs, sessionID: session.id, auto: false })
expect(captured).toContain("<previous-summary>")
expect(captured).toContain("<prior-summary>")
expect(captured).toContain("summary one")
expect(captured.match(/summary one/g)?.length).toBe(1)
expect(captured.indexOf("latest turn")).toBeLessThan(captured.indexOf("<prior-summary>"))
expect(captured).toContain("summary of the conversation before the <conversation> above")
expect(captured).toContain("## Important Details")
expect(captured).toContain("## Work State")
}).pipe(withCompaction({ llm: stub.llmLayer }))
@@ -1440,6 +1466,49 @@ describe("session.compaction.process", () => {
{ git: true },
)
itCompaction.instance(
"keeps plugin context outside the serialized conversation",
() => {
const stub = llm()
let captured = ""
stub.push(
reply("summary", (input) => {
captured = JSON.stringify(input.messages)
}),
)
return Effect.gen(function* () {
const ssn = yield* SessionNs.Service
const session = yield* ssn.create({})
yield* createUserMessage(session.id, "older context")
yield* createUserMessage(session.id, "keep this turn")
yield* createUserMessage(session.id, "and this one too")
yield* createCompactionMarker(session.id)
const msgs = yield* ssn.messages({ sessionID: session.id })
const parent = msgs.at(-1)?.info.id
expect(parent).toBeTruthy()
yield* SessionCompaction.use.process({
parentID: parent!,
messages: msgs,
sessionID: session.id,
auto: false,
})
expect(captured).toContain("Prioritize unresolved migration details")
expect(captured.indexOf("</conversation>")).toBeLessThan(
captured.indexOf("Prioritize unresolved migration details"),
)
}).pipe(
withCompaction({
llm: stub.llmLayer,
plugin: compactionContext("Prioritize unresolved migration details"),
}),
)
},
{ git: true },
)
itCompaction.instance(
"serializes repeated compaction history as one user message",
() => {
+35 -4
View File
@@ -35,10 +35,18 @@ function wrap(message: unknown): ReturnType<NamedError["toObject"]> {
describe("session.retry.delay", () => {
test("caps delay at 30 seconds when headers missing", () => {
const error = apiError()
const delays = Array.from({ length: 10 }, (_, index) => SessionRetry.delay(index + 1, error))
const delays = Array.from({ length: 10 }, (_, index) => SessionRetry.delay(index + 1, error, 0))
expect(delays).toStrictEqual([2000, 4000, 8000, 16000, 30000, 30000, 30000, 30000, 30000, 30000])
})
test("adds jitter to exponential delays", () => {
const error = apiError()
expect(SessionRetry.delay(1, error, 0)).toBe(2000)
expect(SessionRetry.delay(1, error, 1)).toBe(2500)
expect(SessionRetry.delay(4, error, 1)).toBe(20000)
expect(SessionRetry.delay(5, error, 1)).toBe(30000)
})
test("prefers retry-after-ms when shorter than exponential", () => {
const error = apiError({ "retry-after-ms": "1500" })
expect(SessionRetry.delay(4, error)).toBe(1500)
@@ -59,18 +67,18 @@ describe("session.retry.delay", () => {
test("ignores invalid retry hints", () => {
const error = apiError({ "retry-after": "not-a-number" })
expect(SessionRetry.delay(1, error)).toBe(2000)
expect(SessionRetry.delay(1, error, 0)).toBe(2000)
})
test("ignores malformed date retry hints", () => {
const error = apiError({ "retry-after": "Invalid Date String" })
expect(SessionRetry.delay(1, error)).toBe(2000)
expect(SessionRetry.delay(1, error, 0)).toBe(2000)
})
test("ignores past date retry hints", () => {
const pastDate = new Date(Date.now() - 5000).toUTCString()
const error = apiError({ "retry-after": pastDate })
expect(SessionRetry.delay(1, error)).toBe(2000)
expect(SessionRetry.delay(1, error, 0)).toBe(2000)
})
test("uses retry-after values even when exceeding 10 minutes with headers", () => {
@@ -115,6 +123,29 @@ describe("session.retry.delay", () => {
})
}),
)
it.instance("policy stops after five retries", () =>
Effect.gen(function* () {
const attempts: number[] = []
const error = apiError({ "retry-after-ms": "0" })
const step = yield* Schedule.toStepWithMetadata(
SessionRetry.policy({
provider: "test",
parse: Schema.decodeUnknownSync(SessionV1.APIError.Schema),
set: (info) =>
Effect.sync(() => {
attempts.push(info.attempt)
}),
}),
)
yield* Effect.forEach(Array.from({ length: SessionRetry.RETRY_MAX_RETRIES + 1 }), () =>
Effect.ignore(step(error)),
)
expect(attempts).toStrictEqual([1, 2, 3, 4, 5])
}),
)
})
describe("session.retry.retryable", () => {
@@ -102,6 +102,13 @@ describe("session.system", () => {
}
})
test("selects the Kimi prompt for official provider model IDs", () => {
for (const providerID of ["kimi-for-coding", "moonshotai", "moonshotai-cn"]) {
const prompt = SystemPrompt.provider({ providerID, api: { id: "k3" } } as Provider.Model)[0]
expect(prompt).toContain("# Prompt and Tool Use")
}
})
it.effect("skills output is sorted by name and stable across calls", () =>
Effect.gen(function* () {
const prompt = yield* SystemPrompt.Service
@@ -37,8 +37,9 @@ describe("websearch provider", () => {
expect(selectWebSearchProvider(SESSION_ID, { exa: false, parallel: true })).toBe("parallel")
})
test("is only enabled for opencode or explicit websearch provider flags", () => {
test("is enabled for OpenCode providers or explicit websearch provider flags", () => {
expect(webSearchEnabled(ProviderV2.ID.opencode, { exa: false, parallel: false })).toBe(true)
expect(webSearchEnabled(ProviderV2.ID.make("opencode-go"), { exa: false, parallel: false })).toBe(true)
expect(webSearchEnabled(ProviderV2.ID.openai, { exa: false, parallel: false })).toBe(false)
expect(webSearchEnabled(ProviderV2.ID.openai, { exa: true, parallel: false })).toBe(true)
expect(webSearchEnabled(ProviderV2.ID.openai, { exa: false, parallel: true })).toBe(true)
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.18.16",
"version": "1.18.18",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.18.16",
"version": "1.18.18",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/server",
"version": "1.18.16",
"version": "1.18.18",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/session-ui",
"version": "1.18.16",
"version": "1.18.18",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.18.16",
"version": "1.18.18",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/stats-app",
"version": "1.18.16",
"version": "1.18.18",
"private": true,
"type": "module",
"license": "MIT",
@@ -241,7 +241,7 @@ export function Footer(props: {
{ href: "https://opencode.ai/discord", label: i18n.t("footer.community") },
{ href: "https://x.com/opencode", label: "X" },
{ href: githubLink.href, label: i18n.t("header.github") },
{ href: "https://www.youtube.com/@anomaly-co", label: i18n.t("footer.youtube") },
{ href: "https://www.youtube.com/@anomalyco", label: i18n.t("footer.youtube") },
]
const bridge = () =>
props.bridge === undefined
+2 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/stats-core",
"version": "1.18.16",
"version": "1.18.18",
"private": true,
"type": "module",
"license": "MIT",
@@ -12,6 +12,7 @@
"./database": "./src/database.ts",
"./database/*": "./src/database/*.ts",
"./domain/*": "./src/domain/*.ts",
"./r2-sql": "./src/r2-sql.ts",
"./runtime": "./src/runtime.ts",
"./stat-sync": "./src/stat-sync.ts"
},
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { toGeoAggregate, toModelAggregate, toProviderAggregate } from "./inference"
import { buildStatsQueries, toGeoAggregate, toModelAggregate, toProviderAggregate } from "./inference"
import { modelAuthor, normalizeInferenceModel, statModel, statProvider } from "./model-normalization"
describe("inference stat normalization", () => {
@@ -82,6 +82,55 @@ describe("inference stat normalization", () => {
}),
).toMatchObject([{ period_key: "2026-W20" }])
})
test("builds bounded R2 SQL queries for each day and week", () => {
const queries = buildStatsQueries(new Date("2026-08-10T00:00:00.000Z"), new Date("2026-08-12T12:00:00.000Z"), {
namespace: "inference",
table: "generation",
dataset: "zen",
})
expect(queries).toHaveLength(8)
expect(queries[0]).toContain("'week' AS grain")
expect(queries[0]).toContain("'2026-W33' AS period_key")
expect(queries[2]).toContain("'2026-08-10' AS period_key")
expect(queries[6]).toContain("'2026-08-12' AS period_key")
expect(queries[0]).toContain('FROM "inference"."generation"')
expect(queries[0]).toContain("event_type = 'generation.completed'")
expect(queries[0]).toContain("product = 'go'")
expect(queries[0]).toContain("LIMIT 10000")
expect(queries[0]).toContain("approx_distinct(session) AS sessions")
expect(queries[1]).toContain("'geo_model' ELSE 'geo'")
expect(queries[1]).toContain("0 AS sessions")
})
test("aligns periods to UTC calendar boundaries", () => {
const queries = buildStatsQueries(new Date("2026-06-17T15:56:00.000Z"), new Date("2026-06-19T15:56:00.000Z"), {
namespace: "inference",
table: "generation",
dataset: "zen",
})
expect(queries).toHaveLength(8)
expect(queries[0]).toContain("'2026-W25' AS period_key")
expect(queries[0]).toContain("started_at >= '2026-06-15T00:00:00.000Z'")
expect(queries[2]).toContain("'2026-06-17' AS period_key")
expect(queries[2]).toContain("started_at >= '2026-06-17T00:00:00.000Z'")
expect(queries[2]).toContain("started_at < '2026-06-18T00:00:00.000Z'")
expect(queries[6]).toContain("'2026-06-19' AS period_key")
expect(queries[6]).toContain("started_at < '2026-06-19T15:56:00.000Z'")
})
test("uses an exclusive live and legacy source handoff", () => {
const [query] = buildStatsQueries(new Date("2026-08-11T00:00:00.000Z"), new Date("2026-08-12T00:00:00.000Z"), {
namespace: "inference",
table: "generation",
dataset: "zen",
})
expect(query).toContain("(source = 'inference-legacy' AND started_at < '2026-08-11T10:57:48.186Z')")
expect(query).toContain("(source = 'inference' AND started_at >= '2026-08-11T10:57:48.186Z')")
})
})
function aggregate(model: string, provider: string) {

Some files were not shown because too many files have changed in this diff Show More