Compare commits

...
Author SHA1 Message Date
Kit Langton 48be7bec9b Merge branch 'v2' into plugin-registry 2026-10-10 22:30:24 +00:00
opencode-agent[bot] 4617210822 chore(core): refresh bundled models.dev snapshot 2026-10-10 12:19:42 +00:00
Luke Parker 4b6fea2875 chore(ci): build desktop from the unsigned CLI artifact (#53313) 2026-10-10 08:39:37 +00:00
opencode-agent[bot] f714e5f68b chore: update nix node_modules hashes 2026-10-10 08:02:42 +00:00
Kit LangtonandAiden Cline 41b03dea0f chore: upgrade Effect to 4.0.1 (#54198)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-10-10 00:55:24 -07:00
Kit Langton 55dde8810d feat(sdk): seed host plugins before recovery and harden workerd defaults (#54219) 2026-10-09 17:07:51 -07:00
opencode-agent[bot]andrekram1-node 8eff035bf4 fix(tui): highlight C++ module interface files (#53853)
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-10-09 17:29:04 -05:00
Aiden Cline 1a9e29a0b9 fix(core): route Copilot Gemini fallback to chat completions (#54208) 2026-10-09 17:20:14 -05:00
Kit Langton a13c73bb9e fix(core): broadcast wellknown updates globally and bound HTTP timeouts (#53821) 2026-10-09 15:17:26 -07:00
Kit Langton 893d0f2726 fix(tui): retain unavailable saved session model selection (#53822) 2026-10-09 15:17:21 -07:00
Henri-Maxime DucoulombierandAiden Cline c9c5985e9d fix(provider): support CLOUDSDK_CONFIG for Google Vertex ADC (#54164)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2026-10-09 17:04:40 -05:00
Aiden Cline 9f282f8c2c fix(core): accept interleaved Copilot reasoning_opaque values (#54201) 2026-10-09 16:45:57 -05:00
Aiden Cline e0fe07d75c fix(ai): drop stale Vertex location fallback argument (#54202) 2026-10-09 16:12:48 -05:00
Aiden Cline 744aa0cd2a refactor(ai): default Vertex location to global in shared helper (#54171) 2026-10-09 15:43:04 -05:00
Aiden Cline d2905b8dda feat(ai): add Vertex AI Interactions route (#54175) 2026-10-09 14:03:59 -05:00
Aiden Cline 229b422bd6 refactor(ai): pass provider query params through without copying (#54167) 2026-10-09 11:29:23 -05:00
opencode-agent[bot] 5cfe3b4271 chore(core): refresh bundled models.dev snapshot 2026-10-09 12:20:36 +00:00
opencode-agent[bot] 2f3ba04a8b chore: update nix node_modules hashes 2026-10-09 08:46:33 +00:00
Luke Parker f9e892dc85 feat(app): match the session column to the design and use pretty text wrapping (#54020) 2026-10-09 08:40:52 +00:00
Kit Langton 1aae67e853 chore: upgrade Effect to rc.118 (#50231) 2026-10-09 01:36:06 -07:00
Kit Langton 6875ee6c80 fix(core): rebase directory watcher events across symlinked roots 2026-10-09 08:03:15 +00:00
Kit Langton c50e39b72b fix(core): route file overrides through Workspace.connect (#54089) 2026-10-09 01:00:44 -07:00
Shoubhit Dash 903ec2bb35 fix(core): disable effort updates for ChatGPT token sharing (#54085) 2026-10-09 12:52:13 +05:30
Kit Langton ee0e614221 fix(core): preserve subagent notifications across location eviction (#54072) 2026-10-08 23:49:30 -07:00
Aiden Cline 93a7c8404f feat(ai): add Vertex Mistral route (#54058) 2026-10-08 23:51:21 -05:00
Aiden Cline d7f34d4662 refactor(ai): pass provider headers and body through without copying (#54062) 2026-10-08 23:49:24 -05:00
Aiden Cline acd885556b chore(tui): revert execution error row projection from #53826 (#54061) 2026-10-08 23:43:01 -05:00
opencode-agent[bot] 359ad2985b chore: update nix node_modules hashes 2026-10-09 04:36:27 +00:00
Luke Parker 3e3a9b3a49 fix(desktop): restore fast cold dev startup (#54060) 2026-10-09 04:27:16 +00:00
Luke Parker 40cc0f377b fix: surface session execution errors in desktop and TUI timelines (#53826) 2026-10-09 14:14:44 +10:00
Aiden Cline d2d50aaae1 fix(core): shorten Bedrock profile picker copy (#54055) 2026-10-08 23:10:53 -05:00
Aiden Cline eefe85d257 fix(tui): summarize multiple connections as a count (#54054) 2026-10-08 22:04:18 -05:00
Luke Parker f5309982d0 fix(app): hide the closed side region over the side terminal (#54052) 2026-10-09 12:51:35 +10:00
Luke Parker f43903d849 feat(session-ui): deterministic timeline file link detection and resolution (#53641) 2026-10-09 02:50:00 +00:00
Luke Parker 3f8c028572 fix(session-ui): show full tool error text when expanded (#53816) 2026-10-09 12:48:38 +10:00
Aiden Cline ea666727a1 fix(core): add thinking toggle variants for Vertex MaaS models (#54040) 2026-10-08 20:27:23 -05:00
Luke Parker 3a2fa2b9d2 fix(app): keep the browser page up until its still is on screen (#54038) 2026-10-09 01:10:58 +00:00
Luke Parker 5b312918ec fix(session-ui): align the subagent icon with the progress spinner (#54027) 2026-10-09 09:09:46 +10:00
Luke Parker 1a8988efe0 fix(session-ui): reuse complete patch diffs instead of re-diffing (#54017) 2026-10-09 08:49:29 +10:00
usrnk1andLukeParkerDev b5c43a4409 fix(desktop): keep browser panel corner masks aligned (#52461)
Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
2026-10-09 08:13:30 +10:00
Luke Parker a2e3dd2884 fix(app): make the web app installable as a PWA (#53905) 2026-10-09 07:54:07 +10:00
James Long 7d179e83f4 feat(core): move worktree tools into core (#53981) 2026-10-08 17:23:43 -04:00
Luke Parker 886e236b7c fix(app): make third-party notices dialog scrollable (#53911) 2026-10-09 07:06:39 +10:00
Luke Parker 6f01a6e493 fix(app): keep the browser site button clickable after the page takes focus (#53907) 2026-10-09 07:06:20 +10:00
Aiden Cline 2b9cd892ba refactor(core): move provider env key exceptions into plugins (#54005) 2026-10-08 15:20:20 -05:00
opencode 8059cdcbdf sync release versions for v2.0.26 [skip ci] 2026-10-08 19:45:51 +00:00
Aiden Cline 5e73d5cb1f feat(core): add Google Cloud credential setup for Vertex (#53798) 2026-10-08 13:41:52 -05:00
James Long 38c955e1f9 fix(tui): keep low verbosity summaries in place when toggled (#52357) 2026-10-08 14:15:53 -04:00
Daniel Chen 0bd95d0e52 docs(www): remove Fledge Alpha Free from Console models (#53985) 2026-10-08 09:22:17 -07:00
Dax 7522502628 feat(release): lead V2 release notes with highlights (#53969) 2026-10-08 10:35:53 -04:00
fihade 7de2e68ba9 fix(tui): keep the home footer off the hint row in short terminals (#53891) 2026-10-08 14:09:43 +00:00
Shoubhit DashandHona 56ab743289 fix(core): batch tree diff selections on every platform (#53956)
Co-authored-by: Hona <10430890+Hona@users.noreply.github.com>
2026-10-08 19:31:57 +05:30
Dax d44561558f feat(release): write reviewed V2 release notes to CHANGELOG.md (#53964) 2026-10-08 09:59:45 -04:00
Jack 6dfb413917 docs: add Step 5 Free to V2 (#53897) 2026-10-08 21:57:37 +08:00
Dax 19fb8918c6 fix(cli): serve remote access on a random unguessable subdomain (#53962) 2026-10-08 09:53:18 -04:00
Luke Parker a67cf4c3fd fix(core): batch untracked file diffs instead of spawning git per file (#53449) 2026-10-08 17:59:21 +05:30
opencode-agent[bot] 3ff23c314f chore(core): refresh bundled models.dev snapshot 2026-10-08 12:22:14 +00:00
Shoubhit Dash 801c15222b perf(core): speed up and harden snapshot capture (#51996) 2026-10-08 17:46:31 +05:30
opencode-agent[bot] 0621ea9d9f chore: update nix node_modules hashes 2026-10-08 09:21:01 +00:00
opencode 62e087ff7b sync release versions for v2.0.25 [skip ci] 2026-10-08 09:16:16 +00:00
Simon Klee da40b8fbe3 chore: upgrade opentui v0.5.17 (#53904) 2026-10-08 11:11:22 +02:00
Simon Klee 2f1a0e72af fix(tui): open clicked transcript links, including wrapped rows (#53903)
Fixes #44442
Refs #51727, #35649
2026-10-08 08:59:40 +00:00
opencode-agent[bot]andBrendonovich 434f7b2a36 fix(app): derive waiting steer presentation from execution state (#53880)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-10-08 07:37:45 +00:00
Aiden Cline 93afa91904 fix(core): default Vertex location to global (#53872) 2026-10-08 00:42:08 -05:00
opencode-agent[bot] d869df3801 chore: update nix node_modules hashes 2026-10-08 05:34:03 +00:00
opencode-agent[bot]andBrendonovich 9e100f4f88 fix(ui): render dollar math next to punctuation without matching prices (#53860)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-10-08 05:24:29 +00:00
Aiden Cline a19285d10d fix(core): bundle compatible provider entrypoints (#53854) 2026-10-07 23:30:52 -05:00
Ayush Thakur f108fa4928 fix(core): keep last known remote config when refresh fails (#53667)
Fixes #53666

Co-authored-by: Ayush Thakur <51413362+Ayushlm10@users.noreply.github.com>
2026-10-07 23:56:24 -04:00
opencode-agent[bot]andBrendonovich a80ca68c96 fix(app): focus session search when the Home shortcut opens Home (#53761)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-10-08 11:51:02 +08:00
Aiden Cline c9e1ed7080 fix(core): reject malformed tool arguments and settle unfinished calls (#53685) 2026-10-07 22:44:43 -05:00
686 changed files with 17116 additions and 8000 deletions

No files matched your search

+6 -4
View File
@@ -25,7 +25,7 @@ on:
required: false
type: string
release_notes:
description: "Reviewed V2 release notes for the Discord announcement (optional)"
description: "Reviewed V2 release notes for CHANGELOG.md and the Discord announcement (optional)"
required: false
type: string
@@ -348,10 +348,11 @@ jobs:
if-no-files-found: error
build-electron:
# Desktop packaging re-signs the bundled CLI on macOS and Windows, so it starts from the unsigned build.
needs:
- version
- sign-cli-windows
if: github.repository == 'anomalyco/opencode' && (github.ref_name != 'v2' || needs.version.outputs.release != '')
- build-cli
if: github.repository == 'anomalyco/opencode' && ((github.ref_name == 'v2' && needs.version.outputs.release != '') || github.ref_name == 'beta')
continue-on-error: false
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
@@ -391,7 +392,7 @@ jobs:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: opencode-preview-cli
name: opencode-preview-cli-unsigned
path: packages/cli/dist
- uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0
@@ -647,6 +648,7 @@ jobs:
env:
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
OPENCODE_RELEASE_NOTES: ${{ (github.ref_name == 'v2' && inputs.release_notes) || '' }}
AUR_KEY: ${{ secrets.AUR_KEY }}
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
GH_REPO: ${{ needs.version.outputs.repo }}
+39
View File
@@ -0,0 +1,39 @@
# Changelog
Release notes for OpenCode V2, newest first.
## v2.0.26 — 2026-10-08
### Highlights
- **Google Cloud Credentials for Vertex**: Run `/connect`, choose **Vertex**, then select **Google Cloud credentials (gcloud auth or environment)** to use Application Default Credentials. You can choose or enter the Google Cloud project and location. ([#53798](https://github.com/anomalyco/opencode/pull/53798))
- **Faster, Safer Session Snapshots**: Session snapshots now add less overhead, recover from interrupted or concurrent captures, restore large selections faster, and pack stored objects to reduce disk usage. ([#51996](https://github.com/anomalyco/opencode/pull/51996))
- **Working Transcript Links**: You can click links anywhere in a transcript, including any row of a wrapped URL. Hyperlinks also work in more terminals, and `FORCE_HYPERLINK=1` can enable them when automatic detection does not. ([#53903](https://github.com/anomalyco/opencode/pull/53903), [#53904](https://github.com/anomalyco/opencode/pull/53904))
- **Step 5 Preview Free**: You can now select **Step 5 Preview Free** from OpenCode’s free model lineup for a limited time.
### Core
#### Improvements
- Added **Google Cloud credentials (gcloud auth or environment)** to the Vertex connection flow, with project and location choices discovered from environment variables, gcloud configurations, and Application Default Credentials. ([#53798](https://github.com/anomalyco/opencode/pull/53798))
- Reduced session snapshot overhead, accelerated multi-file restores, made capture resilient to crashes and concurrent processes, and added lossless background packing of snapshot objects. ([#51996](https://github.com/anomalyco/opencode/pull/51996))
- Added **Step 5 Preview Free** to OpenCode’s free models for a limited time and removed the retired **Fledge Alpha Free** listing.
#### Bug fixes
- Prevented working-tree diffs from timing out or stalling the server in repositories with many untracked files by batching Git operations. ([#53449](https://github.com/anomalyco/opencode/pull/53449))
- Fixed large revert selections failing when their paths exceeded the operating system’s command-line limit. ([#53956](https://github.com/anomalyco/opencode/pull/53956))
- Fixed snapshot and undo handling for literal filenames containing Git pathspec characters, corrupted or locked snapshot indexes, concurrent captures, and project directories whose names begin with `..`. ([#51996](https://github.com/anomalyco/opencode/pull/51996))
- Changed `opencode service` remote-access URLs to use a persistent random subdomain rather than a predictable route. Disabling and re-enabling remote access generates a new address. ([#53962](https://github.com/anomalyco/opencode/pull/53962))
### TUI
#### Improvements
- Enabled OSC 8 hyperlinks in more terminals, including VS Code and other xterm.js terminals, Zed, mintty, and newer VTE terminals; `FORCE_HYPERLINK=1` now overrides terminal detection. ([#53904](https://github.com/anomalyco/opencode/pull/53904))
#### Bug fixes
- Fixed transcript links not opening when clicked, including URLs wrapped across multiple rows. ([#53903](https://github.com/anomalyco/opencode/pull/53903))
- Kept activity summaries in place when expanding or collapsing details with `session.verbosity: "low"`. ([#52357](https://github.com/anomalyco/opencode/pull/52357))
- Prevented the home-screen version footer from overlapping keyboard hints in short terminals. Thanks [@fihaaade](https://github.com/fihaaade). ([#53891](https://github.com/anomalyco/opencode/pull/53891))
+78 -93
View File
@@ -33,7 +33,7 @@
},
"packages/ai": {
"name": "@opencode/ai",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@aws-sdk/credential-providers": "3.1057.0",
"@opencode/schema": "workspace:*",
@@ -55,7 +55,7 @@
},
"packages/app": {
"name": "@opencode/app",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@corvu/drawer": "catalog:",
"@dnd-kit/abstract": "0.5.0",
@@ -112,7 +112,7 @@
},
"packages/cli": {
"name": "@opencode/cli",
"version": "2.0.24",
"version": "2.0.26",
"bin": {
"opencode": "./bin/opencode.cjs",
"opencode2": "./bin/opencode2.cjs",
@@ -131,7 +131,7 @@
"@opencode/util": "workspace:*",
"@opentui/core": "catalog:",
"@opentui/solid": "catalog:",
"@opentunnel/client": "0.2.0",
"@opentunnel/client": "0.4.0",
"@parcel/watcher": "2.5.1",
"@silvia-odwyer/photon-node": "0.3.4",
"diff": "catalog:",
@@ -180,7 +180,7 @@
},
"packages/client": {
"name": "@opencode/client",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@opencode/protocol": "workspace:*",
"@opencode/schema": "workspace:*",
@@ -195,7 +195,7 @@
"solid-js": "catalog:",
},
"peerDependencies": {
"effect": "4.0.0-rc.112",
"effect": "catalog:",
"solid-js": ">=1.9.0",
},
"optionalPeers": [
@@ -205,7 +205,7 @@
},
"packages/codemode": {
"name": "@opencode/codemode",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"acorn": "8.15.0",
"effect": "catalog:",
@@ -218,7 +218,7 @@
},
"packages/console/app": {
"name": "@opencode/console-app",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -254,7 +254,7 @@
},
"packages/console/core": {
"name": "@opencode/console-core",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -281,7 +281,7 @@
},
"packages/console/function": {
"name": "@opencode/console-function",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@openauthjs/openauth": "0.0.0-20250322224806",
"@opencode/console-core": "workspace:*",
@@ -298,7 +298,7 @@
},
"packages/console/mail": {
"name": "@opencode/console-mail",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -322,7 +322,7 @@
},
"packages/console/support": {
"name": "@opencode/console-support",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@opencode/console-core": "workspace:*",
@@ -342,7 +342,7 @@
},
"packages/core": {
"name": "@opencode/core",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@ai-sdk/cohere": "3.0.27",
"@ai-sdk/perplexity": "3.0.26",
@@ -403,11 +403,12 @@
"@types/node": "catalog:",
"@types/which": "3.0.4",
"drizzle-kit": "catalog:",
"fast-check": "catalog:",
},
},
"packages/desktop": {
"name": "@opencode/desktop",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@zip.js/zip.js": "2.7.62",
"electron-context-menu": "5.0.0",
@@ -437,6 +438,7 @@
"electron": "44.4.5",
"electron-builder": "26.15.7",
"electron-vite": "6.0.0-beta.1",
"magic-string": "0.30.21",
"solid-js": "catalog:",
"typescript": "~5.6.2",
"vite": "8.2.2",
@@ -452,7 +454,7 @@
},
"packages/enterprise": {
"name": "@opencode/enterprise",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@hono/standard-validator": "catalog:",
"@opencode-ai/sdk": "1.18.21",
@@ -489,7 +491,7 @@
},
"packages/function": {
"name": "@opencode/function",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -505,7 +507,7 @@
},
"packages/gui-extensions": {
"name": "@opencode/gui-extensions",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@betteroffice/docx": "0.4.3",
"@betteroffice/fonts": "0.4.0",
@@ -554,9 +556,9 @@
},
"packages/http-recorder": {
"name": "@opencode/http-recorder",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@effect/platform-node-shared": "4.0.0-rc.112",
"@effect/platform-node-shared": "catalog:",
},
"devDependencies": {
"@effect/platform-node": "catalog:",
@@ -573,8 +575,9 @@
},
"packages/httpapi-codegen": {
"name": "@opencode/httpapi-codegen",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@opencode/schema": "workspace:*",
"effect": "catalog:",
"prettier": "3.6.2",
},
@@ -586,7 +589,7 @@
},
"packages/latex": {
"name": "@opencode/latex",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@opencode/plugin": "workspace:*",
"@opentui/core": "catalog:",
@@ -600,7 +603,7 @@
},
"packages/merman": {
"name": "@opencode/merman",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@opencode/plugin": "workspace:*",
"@opentui/core": "catalog:",
@@ -615,7 +618,7 @@
},
"packages/plugin": {
"name": "@opencode/plugin",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@ai-sdk/provider": "3.0.8",
"@opencode/ai": "workspace:*",
@@ -639,8 +642,8 @@
},
"peerDependencies": {
"@opencode/theme": "workspace:*",
"@opentui/core": ">=0.5.16",
"@opentui/solid": ">=0.5.16",
"@opentui/core": ">=0.5.17",
"@opentui/solid": ">=0.5.17",
"solid-js": ">=1.9.0",
},
"optionalPeers": [
@@ -652,7 +655,7 @@
},
"packages/plugin-browser": {
"name": "@opencode/plugin-browser",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@opencode/plugin": "workspace:*",
"@opencode/schema": "workspace:*",
@@ -682,7 +685,7 @@
},
"packages/protocol": {
"name": "@opencode/protocol",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@opencode/schema": "workspace:*",
"effect": "catalog:",
@@ -697,7 +700,7 @@
},
"packages/schema": {
"name": "@opencode/schema",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@standard-schema/spec": "catalog:",
"effect": "catalog:",
@@ -721,7 +724,7 @@
},
"packages/sdk": {
"name": "@opencode/sdk",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@opencode/client": "workspace:*",
"@opencode/core": "workspace:*",
@@ -742,7 +745,7 @@
},
"packages/server": {
"name": "@opencode/server",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@effect/platform-node": "catalog:",
"@effect/platform-node-shared": "catalog:",
@@ -754,16 +757,18 @@
"effect": "catalog:",
},
"devDependencies": {
"@opencode-ai/pty": "0.2.0",
"@opencode/client": "workspace:*",
"@opencode/plugin": "workspace:*",
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
"fast-check": "catalog:",
},
},
"packages/session-ui": {
"name": "@opencode/session-ui",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode/client": "workspace:*",
@@ -798,7 +803,7 @@
},
"packages/simulation": {
"name": "@opencode/simulation",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@opencode/ai": "workspace:*",
"@opencode/core": "workspace:*",
@@ -818,7 +823,7 @@
},
"packages/stats/app": {
"name": "@opencode/stats-app",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@ibm/plex": "6.4.1",
"@kobalte/core": "catalog:",
@@ -852,7 +857,7 @@
},
"packages/stats/core": {
"name": "@opencode/stats-core",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@aws-sdk/client-athena": "3.933.0",
"@planetscale/database": "1.19.0",
@@ -871,7 +876,7 @@
},
"packages/stats/server": {
"name": "@opencode/stats-server",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@aws-sdk/client-firehose": "3.933.0",
"@effect/platform-node": "catalog:",
@@ -917,7 +922,7 @@
},
"packages/theme": {
"name": "@opencode/theme",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@opentui/core": "catalog:",
"effect": "catalog:",
@@ -931,7 +936,7 @@
},
"packages/tui": {
"name": "@opencode/tui",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@opencode/client": "workspace:*",
"@opencode/core": "workspace:*",
@@ -965,7 +970,7 @@
},
"packages/ui": {
"name": "@opencode/ui",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@kitlangton/solid-motion": "0.2.2",
"@kobalte/core": "catalog:",
@@ -975,7 +980,6 @@
"fuzzysort": "catalog:",
"katex": "0.16.47",
"marked": "catalog:",
"marked-katex-extension": "5.1.13",
"marked-shiki": "catalog:",
"motion": "12.34.5",
"remeda": "catalog:",
@@ -1002,7 +1006,7 @@
},
"packages/util": {
"name": "@opencode/util",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@effect/opentelemetry": "catalog:",
"@effect/platform-node": "catalog:",
@@ -1040,7 +1044,7 @@
},
"packages/web": {
"name": "@opencode/web",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -1081,7 +1085,7 @@
},
"services/update": {
"name": "@opencode/update",
"version": "2.0.24",
"version": "2.0.26",
"dependencies": {
"jose": "6.0.11",
"semver": "catalog:",
@@ -1147,10 +1151,10 @@
"catalog": {
"@cloudflare/workers-types": "4.20251008.0",
"@corvu/drawer": "0.2.4",
"@effect/opentelemetry": "4.0.0-rc.112",
"@effect/platform-node": "4.0.0-rc.112",
"@effect/platform-node-shared": "4.0.0-rc.112",
"@effect/sql-sqlite-bun": "4.0.0-rc.112",
"@effect/opentelemetry": "4.0.1",
"@effect/platform-node": "4.0.1",
"@effect/platform-node-shared": "4.0.1",
"@effect/sql-sqlite-bun": "4.0.1",
"@hono/standard-validator": "0.2.0",
"@hono/zod-validator": "0.4.2",
"@kobalte/core": "0.13.13",
@@ -1158,9 +1162,9 @@
"@npmcli/arborist": "9.4.0",
"@octokit/rest": "22.0.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@opentui/core": "0.5.16",
"@opentui/keymap": "0.5.16",
"@opentui/solid": "0.5.16",
"@opentui/core": "0.5.17",
"@opentui/keymap": "0.5.17",
"@opentui/solid": "0.5.17",
"@pierre/diffs": "1.5.1",
"@playwright/test": "1.59.1",
"@sentry/solid": "10.71.0",
@@ -1190,7 +1194,8 @@
"dompurify": "3.4.14",
"drizzle-kit": "1.0.0-rc.5-ab785fc",
"drizzle-orm": "1.0.0-rc.5-169397b",
"effect": "4.0.0-rc.112",
"effect": "4.0.1",
"fast-check": "4.9.0",
"fuzzysort": "3.1.0",
"get-east-asian-width": "1.6.0",
"hono": "4.10.7",
@@ -1728,13 +1733,13 @@
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.12.0", "", {}, "sha512-mlUE+rZ8CatQekLhnaiN91Iemdd+e2gFKooGlnRB3oPTL3VghLfX24dx7HrzMNeC1JrIB/0kpsfyty3f5HNfxQ=="],
"@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-rc.112", "", { "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <2.0.0", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", "effect": "^4.0.0-rc.112" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-OTRv1DxTHUmnakgJ6XVM8wVgF1KgZH4UXnOemwSLUwUjXO+RCikzF8oR/rlVmOGq81KtQzj1URM4M4nchlQOuQ=="],
"@effect/opentelemetry": ["@effect/opentelemetry@4.0.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <2.0.0", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", "effect": "^4.0.1" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-MZ7M1a1dxMqTqeNwIGSECjO7RrReEgeEwHo8BnbOqP+8IfVpdiJi/iPCmkFVWlAxanMl4qhsq69e0Hkul9t+bA=="],
"@effect/platform-node": ["@effect/platform-node@4.0.0-rc.112", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-rc.112", "mime": "^4.1.0", "undici": "^8.10.0" }, "peerDependencies": { "effect": "^4.0.0-rc.112", "redis": ">=5.0.0 <7.0.0" } }, "sha512-/BMAcdNGQQskLmI0Zoa95KfTZkr9HV9N4NSxaSrusG6GeW6Ulp9KvZ+Rlaiw8lnOt43CXjFLdfll5/k5rxL4hQ=="],
"@effect/platform-node": ["@effect/platform-node@4.0.1", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.1", "undici": "^8.11.2" }, "peerDependencies": { "effect": "^4.0.1", "redis": ">=5.0.0 <7.0.0" } }, "sha512-FT0Fu5LO0/XP9zBpj0HqrtJNLluK4StNKqf6TSulUsjp8FPBsDVXGJiDuyoN5J2yVMT7tgvhxSwaDcObWVRIaQ=="],
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-rc.112", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.21.3" }, "peerDependencies": { "effect": "^4.0.0-rc.112" } }, "sha512-ttjz0xKamFN7vL8pNDYVwddJLjZvqKePc05djlz2VcdaKbLsnYbtMnL1rbOfHgEnIUSHGh7FkjaN4DM1Ov81sQ=="],
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.1", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.22.0" }, "peerDependencies": { "effect": "^4.0.1" } }, "sha512-VanaejudEhKJ4mpmqnEzdncVUhN6sGSO9ZDND54mRxAlZxj+4dYAWjDTYAy9o4tJo9aALeP2U8cVcOFzEdSeLg=="],
"@effect/sql-sqlite-bun": ["@effect/sql-sqlite-bun@4.0.0-rc.112", "", { "peerDependencies": { "effect": "^4.0.0-rc.112" } }, "sha512-EqR8pWZo3VzedRvP6qo5HcfqNmFvSa+sQLWQl3oBMx2KY1FCc8NYjep79wYjNkW0qABnffrPUXCMTOncd1A+5g=="],
"@effect/sql-sqlite-bun": ["@effect/sql-sqlite-bun@4.0.1", "", { "peerDependencies": { "effect": "^4.0.1" } }, "sha512-cBP2gD4KRuci02W1t/jzbrsk9spjiy45d7Z5i95nAnYaUXjU4JFgLa9Dxbumg5NUgzoK7qOdIeO0fULxEhK0LA=="],
"@electron-internal/extract-zip": ["@electron-internal/extract-zip@1.0.5", "", {}, "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA=="],
@@ -2070,18 +2075,6 @@
"@motionone/utils": ["@motionone/utils@10.18.0", "", { "dependencies": { "@motionone/types": "^10.17.1", "hey-listen": "^1.0.8", "tslib": "^2.3.1" } }, "sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw=="],
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="],
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="],
"@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="],
"@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="],
"@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="],
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="],
"@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q=="],
@@ -2306,31 +2299,31 @@
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
"@opentui/core": ["@opentui/core@0.5.16", "", { "dependencies": { "bun-ffi-structs": "0.3.1", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.16", "@opentui/core-darwin-x64": "0.5.16", "@opentui/core-linux-arm64": "0.5.16", "@opentui/core-linux-arm64-musl": "0.5.16", "@opentui/core-linux-x64": "0.5.16", "@opentui/core-linux-x64-musl": "0.5.16", "@opentui/core-win32-arm64": "0.5.16", "@opentui/core-win32-x64": "0.5.16" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-RDVip7Ih8Lg5tFkwTDGFqc0TXx8A+rRvm3UuDAqd9YayDx4fIsECBjSS8FDPLYvq5KP1gJB4oY8iGM8WgTbNqw=="],
"@opentui/core": ["@opentui/core@0.5.17", "", { "dependencies": { "bun-ffi-structs": "0.3.1", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.17", "@opentui/core-darwin-x64": "0.5.17", "@opentui/core-linux-arm64": "0.5.17", "@opentui/core-linux-arm64-musl": "0.5.17", "@opentui/core-linux-x64": "0.5.17", "@opentui/core-linux-x64-musl": "0.5.17", "@opentui/core-win32-arm64": "0.5.17", "@opentui/core-win32-x64": "0.5.17" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-75vMaKPKc6tnnUWEultPxiiSU0kqNSdAjbcsFGQecjCF4Z1Jrr4NZBiRo3SoMciai/nq24JCiYcVqPjaKtl/6Q=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mbdp/A2KJOv7hI0b0ezqZx/5vh5G6LwhZnjRW4K3hNcoZM4yC55y/D+L8J960rT4o7QRB3cNXr3NSlnVi1btZA=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ptobzW+1zM29nHvadwy+8uW9jvV8NJr63vcQXWe2rQNtpwN/6k7lEXWiS5LndeBs9AMV5tTsXMEMhttgts4BDQ=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.5.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-D1oA0VBitDHOYrg58GaxQQNAXJ1nSFwu2HQMZklL/LkrqZLX23K/oxgutH/GKvLsyRQNsIVfsXmGZ2LtGiGpxA=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.5.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-6yLbnElIZPxhZSrZSyS4mB5+k0YFlpRO0Q8uX4sLFTmvOy8tIeDPFcTGRQqZQ1Rk938tiAhf7iyAX3P1wL4Liw=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.5.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-ghu4tKIR3wZMZ8Ih/VLeyyG/C7kQgAZuKoGKRSUPcVEF6Qr99yHhtIixBrBoFXfqVm63n5lGimS6KKqbdUQ1yA=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.5.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HO3dYgzQBGJhe58uJauVClIJg3vToYl/LZa7ZzqxQ+3vZ//cxaJ7tgXhHWR2uJE02550yI69e5nqBpSOTT9E8Q=="],
"@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.5.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-t1vppHpf6iR7k+JF6kQtcLKr11gIxm2nH1h9y3eawrG4NCXDQbOvn7XtC6KsT3I1iy0A2Yy+nM0heISziORZlQ=="],
"@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.5.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-QMRdDbmR0LwpmX1HXVbN+V3A44/QsbWzMsF3V7cEz7MaDq6RNBs3CUFqfkPorKgPtI75T0AxM7FdJnjt9C4d0Q=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.5.16", "", { "os": "linux", "cpu": "x64" }, "sha512-y63M7JrjfAQPUwTXogDGqBjS46DfC51LebBUQYVE01/N2F1inybD08qEfxUAFx/Omlf7CXef6liLDdRyTLaohw=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.5.17", "", { "os": "linux", "cpu": "x64" }, "sha512-lNmtQlcwsio4IVo2trc0QoMyun31UsN0mzmSc2BtYMOeOC5e0PyWaEAZWJb/g6AinOs9xwJGL02JAN5J3QPWsw=="],
"@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.5.16", "", { "os": "linux", "cpu": "x64" }, "sha512-lQ1kRwFPBRElG2uFl0afWzpHyUwVEizbV2Ue1IA8DXH15sRCOd7KqeOltpzuKJYIyLey7vLF8SofAe8K1JNnTg=="],
"@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.5.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M2RatQaeET/werHFSH+cg7OkQk/Wn0VoVXZLLEL7DiCDTuhZyMfi3uFDz0+R1IGiiUITqHWpZdZ1LdBY8NCjwQ=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.5.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-cML9+1tqEe+SaB5xlx9jKcYAIG15Eg9sImH/LYyMbm+al7dRBNy5IzdFCldXfwC50blM/goiyzLN1CjTo02vCQ=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.5.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-MyVVpZ0CyGDQ+g4eM0zURqEd/9jDrBIPcZdLyZOmWbdoVu36PLM9KAkqwl/n5ASn/rOrQd+d14jUAsZJvFTqzw=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.5.16", "", { "os": "win32", "cpu": "x64" }, "sha512-1QZ9yHK9xTZBNXVgjPfbdQ94nfw0/KZic9XkuAsNBzUGTkYmfGKZSvPB1YKomBt/1lipRoCdMAnihlS/R7g5eg=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.5.17", "", { "os": "win32", "cpu": "x64" }, "sha512-etkuzyfclRX9wel4ivCOk90dUdZNtjK7U6vr7ZHmwkZyOik34ec3Gse0B/jyk192Xdx0l50Tl2xgC1h68XRiEQ=="],
"@opentui/keymap": ["@opentui/keymap@0.5.16", "", { "dependencies": { "@opentui/core": "0.5.16" }, "peerDependencies": { "@opentui/react": "0.5.16", "@opentui/solid": "0.5.16", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-W78sDuTHMjHa8NwYmoGx5Xy0ztcGsuzrUa/1d24jADj9JCYEJQjABnrWZBRSExsNjiFYV3SxeCKwAPhJli0M3g=="],
"@opentui/keymap": ["@opentui/keymap@0.5.17", "", { "dependencies": { "@opentui/core": "0.5.17" }, "peerDependencies": { "@opentui/react": "0.5.17", "@opentui/solid": "0.5.17", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-y8UdvkOhQVM+e5VlCv/f7m8AxVEWeXoHnjhxZ2SEXA+HKDfWjXjkptAyRhyMZ8wRgtNv2LJiSTHfzelBQk5Syg=="],
"@opentui/solid": ["@opentui/solid@0.5.16", "", { "dependencies": { "@babel/core": "7.29.7", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.5.16", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-gCULWG+HORd5IVydiCchlmULgY1PQccODJvJKDNYJHDmhwyg9/sNSFUxoYajEXLZ40pUIL8ENAK+6p34Z9m/JQ=="],
"@opentui/solid": ["@opentui/solid@0.5.17", "", { "dependencies": { "@babel/core": "7.29.7", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.5.17", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-YShxRqJYyg3ysh4qR2NpCDhr+I1OpIfmcn8nQch7EsBpmUOZh8INqThQI722tXBULOAYhOQWqzr5APjJxYDFbQ=="],
"@opentunnel/client": ["@opentunnel/client@0.2.0", "", { "dependencies": { "@opentunnel/protocol": "0.2.0", "effect": "4.0.0-rc.112" } }, "sha512-UHbLK+y2aPdcTuu4FaCz3Ezcm52Z4TxGp5Hdy9aLYLsz9GUlPpPFaYXjdq8pFyfQlA9qAyI/z5zx5FlZx3aSbw=="],
"@opentunnel/client": ["@opentunnel/client@0.4.0", "", { "dependencies": { "@opentunnel/protocol": "0.4.0" }, "peerDependencies": { "effect": "^4.0.0" } }, "sha512-Y6UiO/ouJ/vtdIy4QmCTRY2DPTRpHy2+jR0F6+AKXoD57ozd8W9ZTTP2J1KDqTb7ZfJmRTuqtx1ZR2TnejlZGQ=="],
"@opentunnel/protocol": ["@opentunnel/protocol@0.2.0", "", { "dependencies": { "effect": "4.0.0-rc.112" } }, "sha512-XQAm3InG70HysML+NiSPxCCfoP+ZzWuxXcCFFFh/qxrvMxuJyOrxsPT49vTo2rDCgrYYdbiFqpz8oXIJh0BYXw=="],
"@opentunnel/protocol": ["@opentunnel/protocol@0.4.0", "", { "peerDependencies": { "effect": "^4.0.0" } }, "sha512-rxmfgJ5O1EYxgzawkOti9DyiM44l0Bkeue5KuHpCFaPAP2n9i4xDYCU8XQp9WAEFt7ifZZqI9giOyzJE61faaA=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -3918,7 +3911,7 @@
"editorconfig": ["editorconfig@1.0.7", "", { "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", "minimatch": "^9.0.1", "semver": "^7.5.3" }, "bin": { "editorconfig": "bin/editorconfig" } }, "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw=="],
"effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="],
"effect": ["effect@4.0.1", "", {}, "sha512-b1VlQG9g8fwxE5QnIZuPoOP/0MsqHJSRKxUijjoM80E5q95FXrX5yWtY5XI8G+GJsBQAVSDbLS458z9a++uVvw=="],
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
@@ -4628,8 +4621,6 @@
"marked": ["marked@18.0.11", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-HnslJfsZkRPBDJRHvVtAaWlZHEpSu7u8LgQuJCELjRKuWR+hpq4A7sLq3p8HaI9ypVoXDXxV34CsQJEe1+J5Aw=="],
"marked-katex-extension": ["marked-katex-extension@5.1.13", "", { "peerDependencies": { "katex": ">=0.16 <0.19", "marked": ">=4 <19" } }, "sha512-+6gVygGg5at9g+YsuIQZk7XcAc6Pg1P0grEJKEWvF7DAwceZ7ZIvU0fhQe6auUqSbnPmSjJxZuICdSCdbGaFRw=="],
"marked-shiki": ["marked-shiki@1.2.1", "", { "peerDependencies": { "marked": ">=7.0.0", "shiki": ">=1.0.0" } }, "sha512-yHxYQhPY5oYaIRnROn98foKhuClark7M373/VpLxiy5TrDu9Jd/LsMwo8w+U91Up4oDb9IXFrP0N1MFRz8W/DQ=="],
"marky": ["marky@1.3.0", "", {}, "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ=="],
@@ -4758,7 +4749,7 @@
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="],
"mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
@@ -4806,10 +4797,6 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"msgpackr": ["msgpackr@2.0.5", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA=="],
"msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="],
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
"mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
@@ -4852,8 +4839,6 @@
"node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
"node-html-parser": ["node-html-parser@7.1.0", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ=="],
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
@@ -5650,7 +5635,7 @@
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
"undici": ["undici@8.10.0", "", {}, "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ=="],
"undici": ["undici@8.11.2", "", {}, "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
@@ -6100,15 +6085,13 @@
"@bufbuild/protoplugin/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="],
"@cloudflare/kv-asset-handler/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
"@cloudflare/vite-plugin/vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="],
"@cloudflare/vite-plugin/ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@effect/platform-node-shared/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="],
"@effect/platform-node-shared/ws": ["ws@8.22.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg=="],
"@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="],
@@ -6690,6 +6673,8 @@
"tw-to-css/tailwindcss": ["tailwindcss@3.3.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.18.2", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w=="],
"unifont/undici": ["undici@8.10.0", "", {}, "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ=="],
"unplugin/acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
"unused-filename/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+3 -3
View File
@@ -1,7 +1,7 @@
{
"nodeModules": {
"x86_64-linux": "sha256-m8XFudp6AwHWDZO7rDXCTkNF0XzgdnWUAg0VCgmGoDQ=",
"aarch64-linux": "sha256-CQmS4coITqsMmtgi/g4Xivy/d6sDIklAE8duCUkAazs=",
"aarch64-darwin": "sha256-fSeacvDBLUCSKIeGCTgZsmqfo2rohARHvFgYLaNXHi8="
"x86_64-linux": "sha256-xUEdCdS+BbEWb2TsUdr088KLYLGqIOBdknGObo3xc+o=",
"aarch64-linux": "sha256-f/MB/0TkxBmW1Q+w2qJe39jiOu5UUHX36VwRBYQWmjs=",
"aarch64-darwin": "sha256-h9An+psN04P9QRfFpkvZByJPdoRrOSJUQIzYu+WJGd0="
}
}
+10 -9
View File
@@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/package.json",
"name": "opencode",
"description": "AI-powered development tool",
"version": "2.0.24",
"version": "2.0.26",
"private": true,
"type": "module",
"packageManager": "bun@1.4.2",
@@ -43,19 +43,19 @@
"packages/stats/*"
],
"catalog": {
"@effect/opentelemetry": "4.0.0-rc.112",
"@effect/platform-node": "4.0.0-rc.112",
"@effect/platform-node-shared": "4.0.0-rc.112",
"@effect/sql-sqlite-bun": "4.0.0-rc.112",
"@effect/opentelemetry": "4.0.1",
"@effect/platform-node": "4.0.1",
"@effect/platform-node-shared": "4.0.1",
"@effect/sql-sqlite-bun": "4.0.1",
"@npmcli/arborist": "9.4.0",
"@types/bun": "1.4.0",
"@types/cross-spawn": "6.0.6",
"@octokit/rest": "22.0.0",
"@hono/standard-validator": "0.2.0",
"@hono/zod-validator": "0.4.2",
"@opentui/core": "0.5.16",
"@opentui/keymap": "0.5.16",
"@opentui/solid": "0.5.16",
"@opentui/core": "0.5.17",
"@opentui/keymap": "0.5.17",
"@opentui/solid": "0.5.17",
"@tanstack/solid-virtual": "3.13.37",
"@shikijs/stream": "4.4.3",
"@standard-schema/spec": "1.1.0",
@@ -80,7 +80,8 @@
"dompurify": "3.4.14",
"drizzle-kit": "1.0.0-rc.5-ab785fc",
"drizzle-orm": "1.0.0-rc.5-169397b",
"effect": "4.0.0-rc.112",
"effect": "4.0.1",
"fast-check": "4.9.0",
"ai": "6.0.168",
"cross-spawn": "7.0.6",
"hono": "4.10.7",
+7 -1
View File
@@ -32,6 +32,11 @@ Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployme
`Google.configure({ apiKey }).interactions(modelID)` selects the Interactions API; `.model(modelID)` still selects
GenerateContent. The package entrypoint is `@opencode/ai/providers/google/interactions`.
For Vertex AI, use `GoogleVertexInteractions.configure({ project, location: "global" }).model(modelID)` or
`@opencode/ai/providers/google-vertex/interactions`. It uses Application Default Credentials by default, accepts
an explicit `accessToken` or `auth` override, and supports Express mode with `apiKey`. Vertex metadata and raw usage
live under `providerMetadata.vertex`. The default is still full-history replay with `store: false`.
```ts
const model = Google.configure({ apiKey }).interactions("gemini-3.8-flash")
const response = yield* LLM.generate({
@@ -1316,13 +1321,14 @@ APIs have separate entrypoints:
- `@opencode/ai/providers/openai-compatible/responses`
- `@opencode/ai/providers/anthropic-compatible`
- `@opencode/ai/providers/google-vertex/gemini`
- `@opencode/ai/providers/google-vertex/interactions`
- `@opencode/ai/providers/google-vertex/chat`
- `@opencode/ai/providers/google-vertex/responses`
- `@opencode/ai/providers/google-vertex/messages`
OpenAI Responses has one semantic route and uses HTTP by default. Advanced callers may supply a per-call WebSocket channel executor through `StreamOptions`; transport policy does not change provider settings, model identity, or route identity. The provider-neutral Open Responses implementation owns the reusable WebSocket request and event contract, while each provider opts in with its own handshake and connection policy. Azure follows the same Chat/Responses split at `providers/azure/chat` and `providers/azure/responses`. Generic OpenAI-compatible Chat remains at `providers/openai-compatible`; the Responses adapter at `providers/openai-compatible/responses` uses the provider-neutral Open Responses protocol. OpenAI Responses extends that baseline with OpenAI tools, event variants, metadata, and defaults. Generic Anthropic Messages-compatible providers use `providers/anthropic-compatible`, which the named Anthropic provider composes. Google Gemini and Amazon Bedrock expose their single native API through their existing provider paths.
Vertex Gemini, Vertex Chat, Vertex Responses, and Vertex Messages are separate API entrypoints. All accept `project`, `location`, and an optional `accessToken`; when no explicit token or auth override is supplied they lazily use Google Application Default Credentials. Vertex Gemini instead selects express mode when `apiKey` or `GOOGLE_VERTEX_API_KEY` is present. Vertex Chat targets MaaS models through the OpenAI-compatible Chat Completions endpoint, while Vertex Responses targets Grok models and defaults `store` to `false` as required by Vertex. `providers/google-vertex` remains the default alias for `providers/google-vertex/gemini`.
Vertex Gemini, Vertex Interactions, Vertex Chat, Vertex Responses, and Vertex Messages are separate API entrypoints. All accept `project`, `location`, and an optional `accessToken`; when no explicit token or auth override is supplied they lazily use Google Application Default Credentials. Vertex Gemini and Interactions instead select express mode when `apiKey` or `GOOGLE_VERTEX_API_KEY` is present (explicit `project` or `location` selects ADC over an ambient key). Vertex Chat targets MaaS models through the OpenAI-compatible Chat Completions endpoint, while Vertex Responses targets Grok models and defaults `store` to `false` as required by Vertex. `providers/google-vertex` remains the default alias for `providers/google-vertex/gemini`.
Tuned Vertex Gemini deployments use model ids shaped like `endpoints/1234567890` and require OAuth or ADC; Vertex express-mode API keys support publisher models only.
+1 -1
View File
@@ -15,7 +15,7 @@ import { OpenAI } from "@opencode/ai/providers"
* hover imports and local values to see how the public API is typed.
*/
const apiKey = Config.redacted("OPENAI_API_KEY")
const apiKey = Config.Redacted("OPENAI_API_KEY")
// 1. Configure a provider. The configured facade records provider identity,
// deployment options, authentication, and defaults. Per-modality selectors pick
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "2.0.24",
"version": "2.0.26",
"name": "@opencode/ai",
"type": "module",
"license": "MIT",
+2 -2
View File
@@ -5,7 +5,7 @@ import * as path from "node:path"
import * as prompts from "@clack/prompts"
import { AwsV4Signer } from "aws4fetch"
import { Config, ConfigProvider, Effect, FileSystem, PlatformError, Redacted } from "effect"
import { FetchHttpClient, HttpClient, HttpClientRequest, type HttpClientResponse } from "effect/unstable/http"
import { FetchHttpClient, HttpClient, HttpClientRequest, type HttpClientResponse } from "effect/http"
import * as ProviderShared from "../src/protocols/shared"
import { CloudflareAIGateway } from "../src/providers/cloudflare-ai-gateway.js"
import { CloudflareWorkersAI } from "../src/providers/cloudflare-workers-ai.js"
@@ -383,7 +383,7 @@ const readEnvFile = Effect.fn("RecordingEnv.readFile")(function* () {
})
const readConfigString = (provider: ConfigProvider.ConfigProvider, name: string) =>
Config.string(name)
Config.String(name)
.parse(provider)
.pipe(
Effect.match({
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { Headers, HttpClientRequest } from "effect/http"
import {
ChoiceQuestion,
EvaluationInput,
+7 -6
View File
@@ -1,7 +1,8 @@
export * as Media from "./media.js"
import { Effect, Encoding, FileSystem, Schema, SchemaGetter } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { Effect, FileSystem, Schema, SchemaGetter } from "effect"
import { Base64 } from "effect/encoding"
import { HttpClientRequest } from "effect/http"
import { ProviderID } from "./schema/ids.js"
import { AIError, HttpContext, InvalidProviderOutputError, InvalidRequestError } from "./schema/errors.js"
import { ProviderMetadata } from "./schema/options.js"
@@ -140,7 +141,7 @@ export class Asset {
inline(): Inline | undefined {
const source = this.source
if (source.type !== "bytes" && source.type !== "base64") return undefined
const base64 = source.type === "base64" ? source.data : (this.#base64 ??= Encoding.encodeBase64(source.data))
const base64 = source.type === "base64" ? source.data : (this.#base64 ??= Base64.encode(source.data))
const mime = this.mediaType.toLowerCase()
return { mime, base64, dataUrl: `data:${mime};base64,${base64}` }
}
@@ -155,7 +156,7 @@ export class Asset {
return Effect.fail(invalid(`Cannot materialize provider ref ${source.provider}:${source.id}`))
const decoded =
source.type === "base64"
? Effect.fromResult(Encoding.decodeBase64(source.data)).pipe(
? Effect.fromResult(Base64.decode(source.data)).pipe(
Effect.mapError((cause) => invalid(`Media asset contains invalid base64 data`, cause)),
)
: download(source, this.headers)
@@ -168,7 +169,7 @@ export class Asset {
const source = this.source
if (source.type === "base64") return Effect.succeed(source.data)
if (this.#base64 !== undefined) return Effect.succeed(this.#base64)
return this.bytes().pipe(Effect.map((data) => (this.#base64 = Encoding.encodeBase64(data))))
return this.bytes().pipe(Effect.map((data) => (this.#base64 = Base64.encode(data))))
})
}
@@ -183,7 +184,7 @@ export class Asset {
toJSON() {
const source = this.source
return {
source: source.type === "bytes" ? { ...source, data: Encoding.encodeBase64(source.data) } : source,
source: source.type === "bytes" ? { ...source, data: Base64.encode(source.data) } : source,
info: this.info,
providerMetadata: this.providerMetadata,
}
@@ -6,7 +6,7 @@ import { Auth } from "../route/auth.js"
import { Endpoint } from "../route/endpoint.js"
import { Framing } from "../route/framing.js"
import { Protocol } from "../route/protocol.js"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { HttpTransport } from "../route/transport/index.js"
import {
AIError,
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
@@ -1,4 +1,5 @@
import { Effect, Encoding, Schema } from "effect"
import { Effect, Schema } from "effect"
import { Base64 } from "effect/encoding"
import { Route } from "../route/client.js"
import { Endpoint } from "../route/endpoint.js"
import { Protocol } from "../route/protocol.js"
@@ -612,7 +613,7 @@ interface ParserState {
readonly reasoningRedactedContent: Readonly<Record<number, Uint8Array[]>>
}
const encodeRedactedContent = (chunks: ReadonlyArray<Uint8Array>) => Encoding.encodeBase64(concatBytes(chunks))
const encodeRedactedContent = (chunks: ReadonlyArray<Uint8Array>) => Base64.encode(concatBytes(chunks))
const step = (state: ParserState, event: BedrockEvent) =>
Effect.gen(function* () {
@@ -661,7 +662,7 @@ const step = (state: ParserState, event: BedrockEvent) =>
const events: LLMEvent[] = []
const redactedChunk = yield* (() => {
if (reasoning.redactedContent === undefined) return Effect.succeed(undefined)
return Effect.fromResult(Encoding.decodeBase64(reasoning.redactedContent)).pipe(
return Effect.fromResult(Base64.decode(reasoning.redactedContent)).pipe(
Effect.mapError((cause) =>
ProviderShared.eventError(
ADAPTER,
@@ -1,6 +1,7 @@
import { EventStreamCodec } from "@smithy/eventstream-codec"
import { fromUtf8, toUtf8 } from "@smithy/util-utf8"
import { Effect, Encoding, Stream } from "effect"
import { Effect, Stream } from "effect"
import { Base64 } from "effect/encoding"
import { AIError, AIErrorReason, InvalidProviderOutputError } from "../schema/index.js"
import { Framing } from "../route/framing.js"
import { ProviderShared } from "./shared.js"
@@ -47,7 +48,7 @@ const consumeFrames = (route: string) => (state: FrameBufferState, input: FrameI
route,
classification: "incomplete-stream",
message: `Incomplete Bedrock Converse event-stream frame: ${remaining.length} buffered bytes remain at end of stream`,
body: Encoding.encodeBase64(remaining),
body: Base64.encode(remaining),
}),
})
return [state, []] as const
@@ -68,7 +69,7 @@ const consumeFrames = (route: string) => (state: FrameBufferState, input: FrameI
`Failed to decode Bedrock Converse event-stream frame: ${
error instanceof Error ? error.message : String(error)
}`,
Encoding.encodeBase64(view.subarray(0, totalLength)),
Base64.encode(view.subarray(0, totalLength)),
error,
),
})
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { Media } from "../media.js"
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
import { mergeJsonRecords, type OpenString } from "../schema/index.js"
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
import { mergeJsonRecords, type OpenString } from "../schema/index.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Duration, Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+3 -2
View File
@@ -1,4 +1,5 @@
import { Effect, Encoding, Schema } from "effect"
import { Effect, Schema } from "effect"
import { Base64 } from "effect/encoding"
import { Protocol } from "../route/protocol.js"
import { LLMEvent, LLMRequest, Message, ToolResultPart } from "../schema/index.js"
import { OpenResponses } from "./open-responses.js"
@@ -113,7 +114,7 @@ const HOSTED_TOOLS = {
"Meta returned an image without data",
ProviderShared.encodeJson(raw),
)
const data = yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(
const data = yield* Effect.fromResult(Base64.decode(item.result)).pipe(
Effect.mapError((cause) =>
ProviderShared.eventError(
ADAPTER,
+1 -1
View File
@@ -125,7 +125,7 @@ export type ProviderOptionsInput = {
readonly [key: string]: unknown
}
const MistralBody = Schema.Struct({
export const MistralBody = Schema.Struct({
model: Schema.String,
messages: Schema.Array(MistralMessage),
tools: optionalArray(MistralTool),
@@ -1,5 +1,5 @@
import { Effect, Schema, Stream } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { Framing } from "../route/framing.js"
import type { HttpContext } from "../schema/index.js"
import {
@@ -1,5 +1,6 @@
import { Effect, Encoding, Schema } from "effect"
import { Headers } from "effect/unstable/http"
import { Effect, Schema } from "effect"
import { Base64 } from "effect/encoding"
import { Headers } from "effect/http"
import { Route } from "../route/client.js"
import { Auth } from "../route/auth.js"
import { Endpoint } from "../route/endpoint.js"
@@ -240,7 +241,7 @@ const checkpointBody = {
const hostedToolResult = Effect.fnUntraced(function* (item: ResponsesHostedTools.Item) {
const isError = item.error !== undefined && item.error !== null
if (item.type === "image_generation_call" && item.result) {
yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(
yield* Effect.fromResult(Base64.decode(item.result)).pipe(
Effect.mapError((cause) =>
ProviderShared.eventError(ADAPTER, "OpenAI Responses returned invalid image base64", undefined, cause),
),
@@ -1,5 +1,5 @@
import { Duration, Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { Media } from "../media.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Duration, Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { Tool } from "@opencode/schema/tool"
import { Effect, Option, Schema } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { Headers, HttpClientRequest } from "effect/http"
import { Media } from "../media.js"
import {
InvalidProviderOutputError,
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
@@ -1,6 +1,6 @@
import { AwsV4Signer } from "aws4fetch"
import { Effect } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { Auth, type AuthInput } from "../../route/auth.js"
import { AIError, AuthenticationError } from "../../schema/index.js"
import { ProviderShared } from "../shared.js"
@@ -1,4 +1,5 @@
import { Effect, Encoding, Schema } from "effect"
import { Effect, Schema } from "effect"
import { Base64 } from "effect/encoding"
import type { MediaPart } from "../../schema/index.js"
import { ProviderShared } from "../shared.js"
@@ -78,12 +79,12 @@ function documentName(filename: string | undefined, names: Set<string>) {
const mediaBase64 = Effect.fnUntraced(function* (part: MediaPart) {
const media = yield* ProviderShared.requireInlineMedia("Bedrock Converse", part.media)
const bytes = yield* Effect.fromResult(Encoding.decodeBase64(media.base64)).pipe(
const bytes = yield* Effect.fromResult(Base64.decode(media.base64)).pipe(
Effect.mapError((cause) =>
ProviderShared.invalidRequest("Bedrock Converse media data must be valid base64", cause),
),
)
return Encoding.encodeBase64(bytes)
return Base64.encode(bytes)
})
// Route by MIME. Known image/document formats lower into a typed block; anything
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../../generation.js"
import type { Media } from "../../media.js"
import { MediaProtocol } from "../../route/media-protocol.js"
@@ -1,4 +1,5 @@
import { Effect, Encoding } from "effect"
import { Effect } from "effect"
import { Base64 } from "effect/encoding"
import { Media } from "../../media.js"
import type { MediaProtocol } from "../../route/media-protocol.js"
import { mergeJsonRecords, type AIError, type ProviderID } from "../../schema/index.js"
@@ -10,7 +11,7 @@ export const inlineBytes = (route: string, asset: Media.Asset): Effect.Effect<Ui
if (asset.source.type === "bytes") return Effect.succeed(asset.source.data)
const inline = asset.inline()
if (!inline) return Effect.fail(ProviderShared.inlineRequired(route, asset))
return Effect.fromResult(Encoding.decodeBase64(inline.base64)).pipe(
return Effect.fromResult(Base64.decode(inline.base64)).pipe(
Effect.mapError((cause) => ProviderShared.invalidRequest(`${route} media contains invalid base64 data`, cause)),
)
}
@@ -52,7 +53,7 @@ export const decodedAsset = (
mediaType: string | undefined,
options?: Media.AssetOptions,
) =>
Effect.fromResult(Encoding.decodeBase64(data)).pipe(
Effect.fromResult(Base64.decode(data)).pipe(
Effect.mapError((cause) => invalid(`${label} contains invalid base64 data`, cause)),
Effect.map((bytes) => Media.bytes(bytes, mediaType, options)),
)
+10 -2
View File
@@ -54,9 +54,17 @@ const MODEL_NAMES = [
] as const
// Tool arguments are always a JSON object, and most providers reject a tool schema whose root does not
// declare `type: "object"`, such as `{}` or a bare `properties` map. Effect encodes an empty struct as
// `anyOf` object or array; every object matches its bare object branch, so that `anyOf` is dropped.
// declare `type: "object"`, such as `{}` or a bare `properties` map. An object root makes Effect's
// empty-struct markers redundant: `{ not: { type: "null" } }`, and `anyOf` object or array from older
// releases. Both are dropped so the root is a plain object schema.
const objectRoot = (schema: JsonSchema): JsonSchema => {
if (
(schema.type === undefined || schema.type === "object") &&
isRecord(schema.not) &&
schema.not.type === "null" &&
Object.keys(schema.not).length === 1
)
return { type: "object", ...Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "not")) }
if (schema.type !== undefined) return schema
if (
Array.isArray(schema.anyOf) &&
+8 -13
View File
@@ -1,5 +1,5 @@
import { Effect, Option } from "effect"
import { AIError, LLMEvent, type ProviderMetadata, type ToolCall } from "../../schema/index.js"
import { AIError, LLMEvent, type ProviderMetadata, type ToolCall, type ToolInputError } from "../../schema/index.js"
import { eventError, parseToolInput, type ToolAccumulator } from "../shared.js"
import { parse } from "./partial-json.js"
@@ -79,16 +79,6 @@ const inputDelta = (tool: PendingTool, text: string): LLMEvent => {
const toolCall = (route: string, tool: PendingTool, inputOverride?: string) => {
const raw = inputOverride ?? tool.input
return parseToolInput(route, tool.name, raw).pipe(
Effect.catch((error) =>
tool.providerExecuted
? Effect.fail(error)
: Effect.succeed(
Option.getOrElse(
Option.map(parsePartialInput(raw), (input) => input ?? {}),
() => ({}),
),
),
),
Effect.map(
(input): ToolCall =>
LLMEvent.toolCall({
@@ -100,10 +90,15 @@ const toolCall = (route: string, tool: PendingTool, inputOverride?: string) => {
providerMetadata: tool.providerMetadata,
}),
),
Effect.catch((error) =>
tool.providerExecuted
? Effect.fail(error)
: Effect.succeed(LLMEvent.toolInputError({ id: tool.id, name: tool.name, namespace: tool.namespace, raw })),
),
)
}
const finishEvents = (tool: PendingTool, event: ToolCall): ReadonlyArray<LLMEvent> => [
const finishEvents = (tool: PendingTool, event: ToolCall | ToolInputError): ReadonlyArray<LLMEvent> => [
LLMEvent.toolInputEnd({
id: tool.id,
name: tool.name,
@@ -206,7 +201,7 @@ export const appendExisting = <K extends StreamKey>(
/**
* Finalize one pending tool call: parse the accumulated raw JSON, remove it
* from state, and recover incomplete local arguments when needed.
* from state, and reject malformed local arguments without repairing them.
* Missing keys are a no-op because some providers emit stop events for
* non-tool content blocks.
*/
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Duration, Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
@@ -164,8 +164,8 @@ const fromSettings = ({
baseURL,
credentials,
generation: topP === undefined ? undefined : { topP },
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
profile,
providerOptions,
region,
@@ -74,8 +74,8 @@ export const model: ProviderPackage.Definition<Settings, AnthropicMessages.Provi
return configure({
...(authToken === undefined ? { apiKey: apiKey } : { auth: Auth.bearer(authToken) }),
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
provider,
providerOptions,
}).model(modelID)
+2 -2
View File
@@ -64,8 +64,8 @@ export const model: ProviderPackage.Definition<Settings, AnthropicMessages.Provi
return configure({
...(authToken === undefined ? { apiKey: apiKey } : { auth: Auth.bearer(authToken) }),
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
}
+3 -3
View File
@@ -159,10 +159,10 @@ const config = ({
const common = {
apiKey,
apiVersion,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
queryParams: queryParams === undefined ? undefined : { ...queryParams },
queryParams,
useDeploymentBasedUrls,
}
if (baseURL !== undefined) return { ...common, baseURL }
+2 -2
View File
@@ -55,8 +55,8 @@ export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsIn
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
+2 -2
View File
@@ -64,7 +64,7 @@ export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsIn
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
@@ -166,8 +166,8 @@ export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsIn
collectLog,
gatewayApiKey,
gatewayId,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
metadata,
providerOptions,
skipCache,
@@ -68,8 +68,8 @@ export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsIn
return configure({
apiKey,
baseURL: baseURL(settings),
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
}
+1 -1
View File
@@ -60,7 +60,7 @@ export const model: ProviderPackage.Definition<Settings, CohereChat.ProviderOpti
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
export * as Cohere from "./cohere.js"
+1 -1
View File
@@ -11,6 +11,6 @@ export const model: ProviderPackage.Definition<Settings, Cohere.ChatOptionsInput
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).chat(modelID)
+2 -2
View File
@@ -67,7 +67,7 @@ export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsIn
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
+2 -2
View File
@@ -59,8 +59,8 @@ export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsIn
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
+2 -2
View File
@@ -56,8 +56,8 @@ export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsIn
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
@@ -47,7 +47,7 @@ const configuredRoute = (input: Config) => {
project: inputProject,
...rest
} = input
const location = GoogleVertexShared.location(inputLocation, "global")
const location = GoogleVertexShared.location(inputLocation)
const project = GoogleVertexShared.project(inputProject)
return route.with({
...rest,
@@ -83,8 +83,8 @@ export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsIn
return configure({
accessToken,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
location,
project,
providerOptions,
@@ -0,0 +1,105 @@
import type { ProviderPackage } from "../provider-package.js"
import { GoogleInteractions } from "../protocols/google-interactions.js"
import { Auth } from "../route/auth.js"
import { Route, type RouteDefaultsInput } from "../route/client.js"
import { Endpoint } from "../route/endpoint.js"
import { Framing } from "../route/framing.js"
import { ProviderConfigurationError, ProviderID, type ModelID } from "../schema/index.js"
import { GoogleVertexShared } from "./google-vertex-shared.js"
export type GoogleInteractionsOptionsInput = GoogleInteractions.OptionsInput
export const id = ProviderID.make("google-vertex")
export type Config = RouteDefaultsInput &
GoogleVertexShared.ApiKeyOptions & {
readonly baseURL?: string
readonly location?: string
readonly project?: string
readonly providerOptions?: GoogleInteractions.ProviderOptionsInput
}
export type Settings = ProviderPackage.Settings &
GoogleInteractions.ProviderOptionsInput &
(
| { readonly accessToken?: string; readonly apiKey?: never }
| { readonly accessToken?: never; readonly apiKey?: string }
) & {
readonly baseURL?: string
readonly location?: string
readonly project?: string
}
const route = Route.make({
id: "google-vertex-interactions",
provider: id,
providerMetadataKey: "vertex",
protocol: GoogleInteractions.protocol,
endpoint: Endpoint.path("/interactions", { query: { alt: "sse" } }),
auth: Auth.none,
headers: ({ request }): Record<string, string> => {
const serviceTier = request.providerOptions?.serviceTier
return typeof serviceTier === "string" ? { "x-vertex-ai-llm-shared-request-type": serviceTier } : {}
},
framing: Framing.sse,
})
export const routes = [route]
const configuredRoute = (input: Config) => {
const {
accessToken: _accessToken,
apiKey: _apiKey,
auth: _auth,
baseURL,
location: inputLocation,
project: inputProject,
...rest
} = input
const apiKey = GoogleVertexShared.apiKey(input)
const location = GoogleVertexShared.location(inputLocation)
const project = GoogleVertexShared.project(inputProject)
return route.with({
...rest,
endpoint: {
baseURL:
baseURL ??
`https://${GoogleVertexShared.host(location)}/v1beta1/${apiKey === undefined ? `projects/${GoogleVertexShared.requireProject(project)}/` : ""}locations/${location}`,
},
auth: apiKey === undefined ? GoogleVertexShared.oauth(input, project) : Auth.header("x-goog-api-key", apiKey),
})
}
export const configure = (input: Config = {}) => {
const route = configuredRoute(input)
return {
id,
model: (modelID: string | ModelID) => route.model<GoogleInteractions.ProviderOptionsInput>({ id: modelID }),
configure,
}
}
export const provider = {
id,
configure,
}
export const model: ProviderPackage.Definition<Settings, GoogleInteractions.ProviderOptionsInput>["model"] = (
modelID,
{ accessToken, apiKey, baseURL, body, headers, location, project, ...providerOptions },
) => {
if (apiKey !== undefined && accessToken !== undefined)
throw new ProviderConfigurationError({
provider: id,
message: "Google Vertex apiKey cannot be combined with accessToken or auth",
})
return configure({
...(apiKey === undefined ? { accessToken } : { apiKey }),
baseURL,
headers,
http: body === undefined ? undefined : { body },
location,
project,
providerOptions,
}).model(modelID)
}
@@ -77,7 +77,7 @@ const configuredRoute = (input: Config) => {
project: inputProject,
...rest
} = input
const location = GoogleVertexShared.location(inputLocation, "global")
const location = GoogleVertexShared.location(inputLocation)
const project = GoogleVertexShared.project(inputProject)
return route.with({
...rest,
@@ -113,8 +113,8 @@ export const model: ProviderPackage.Definition<Settings, AnthropicMessages.Provi
return configure({
accessToken,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
location,
project,
providerOptions,
@@ -0,0 +1,108 @@
import { Effect, Schema, Struct } from "effect"
import type { ProviderPackage } from "../provider-package.js"
import { MistralChat } from "../protocols/mistral-chat.js"
import { Auth } from "../route/auth.js"
import { Route, type RouteDefaultsInput } from "../route/client.js"
import { Endpoint } from "../route/endpoint.js"
import { Protocol } from "../route/protocol.js"
import { HttpTransport } from "../route/transport/index.js"
import { ProviderConfigurationError, ProviderID, type ModelID } from "../schema/index.js"
import { GoogleVertexShared } from "./google-vertex-shared.js"
export const id = ProviderID.make("google-vertex")
export type Config = RouteDefaultsInput &
GoogleVertexShared.OAuthOptions & {
readonly baseURL?: string
readonly location?: string
readonly project?: string
readonly providerOptions?: MistralChat.ProviderOptionsInput
}
export type Settings = ProviderPackage.Settings &
MistralChat.ProviderOptionsInput & {
readonly accessToken?: string
readonly apiKey?: never
readonly baseURL?: string
readonly location?: string
readonly project?: string
}
type Body = Omit<MistralChat.MistralBody, "prompt_cache_key">
// Mistral on Vertex takes the Mistral chat body, model included, through the publisher's streamRawPredict method.
// Vertex validates the body strictly and rejects prompt_cache_key, which Mistral's own API accepts.
const route = Route.make({
id: "google-vertex-mistral",
provider: id,
providerMetadataKey: "vertex",
protocol: Protocol.make({
id: MistralChat.protocol.id,
body: {
schema: Schema.Struct(Struct.omit(MistralChat.MistralBody.fields, ["prompt_cache_key"])),
from: (request) =>
MistralChat.protocol.body.from(request).pipe(Effect.map((body) => Struct.omit(body, ["prompt_cache_key"]))),
},
stream: MistralChat.protocol.stream,
}),
endpoint: Endpoint.path(({ request }) => `/${request.model.id}:streamRawPredict`),
auth: Auth.none,
transport: HttpTransport.sseJson.with<Body>().with({ framing: MistralChat.framing }),
})
export const routes = [route]
const configuredRoute = (input: Config) => {
if ("apiKey" in input && input.apiKey !== undefined)
throw new ProviderConfigurationError({ provider: id, message: "Google Vertex Mistral does not support API keys" })
const {
accessToken: _accessToken,
auth: _auth,
baseURL,
location: inputLocation,
project: inputProject,
...rest
} = input
const location = GoogleVertexShared.location(inputLocation)
const project = GoogleVertexShared.project(inputProject)
return route.with({
...rest,
endpoint: {
baseURL:
baseURL ??
`https://${GoogleVertexShared.host(location)}/v1/projects/${GoogleVertexShared.requireProject(project)}/locations/${location}/publishers/mistralai/models`,
},
auth: GoogleVertexShared.oauth(input, project),
})
}
export const configure = (input: Config = {}) => {
const route = configuredRoute(input)
return {
id,
model: (modelID: string | ModelID) => route.model<MistralChat.ProviderOptionsInput>({ id: modelID }),
configure,
}
}
export const provider = {
id,
configure,
}
export const model: ProviderPackage.Definition<Settings, MistralChat.ProviderOptionsInput>["model"] = (
modelID,
{ accessToken, apiKey, baseURL, body, headers, location, project, ...providerOptions },
) => {
if (apiKey !== undefined)
throw new ProviderConfigurationError({ provider: id, message: "Google Vertex Mistral does not support API keys" })
return configure({
accessToken,
baseURL,
headers,
http: body === undefined ? undefined : { body },
location,
project,
providerOptions,
}).model(modelID)
}
@@ -48,7 +48,7 @@ const configuredRoute = (input: Config) => {
project: inputProject,
...rest
} = input
const location = GoogleVertexShared.location(inputLocation, "global")
const location = GoogleVertexShared.location(inputLocation)
const project = GoogleVertexShared.project(inputProject)
return route.with({
...rest,
@@ -84,8 +84,8 @@ export const model: ProviderPackage.Definition<Settings, OpenResponsesProviderOp
return configure({
accessToken,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
location,
project,
providerOptions,
@@ -1,4 +1,4 @@
import type { AnyAuthClient } from "google-auth-library"
import type { AnyAuthClient, GoogleAuthOptions } from "google-auth-library"
import { Effect, Redacted } from "effect"
import { Auth, MissingCredentialError } from "../route/auth.js"
import { ProviderConfigurationError, ProviderID } from "../schema/index.js"
@@ -6,6 +6,31 @@ import { ProviderConfigurationError, ProviderID } from "../schema/index.js"
const SCOPE = "https://www.googleapis.com/auth/cloud-platform"
const id = ProviderID.make("google-vertex")
export const loadADCClient = async (project?: string) => {
const { GoogleAuth } = await import("google-auth-library")
// google-auth-library 10.5.0 ignores CLOUDSDK_CONFIG during ADC file discovery.
// Fix this in shipped AI code: a repository-level Bun dependency patch would not
// reach npm or Bun consumers of the published @opencode/ai package.
// Override only the well-known-file lookup: keyFilename bypasses ADC's quota-project
// preparation. Keep explicit credentials and metadata fallback in Google's ADC chain.
class CloudSDKAuth extends GoogleAuth {
override async _tryGetApplicationCredentialsFromWellKnownFile(options?: GoogleAuthOptions["clientOptions"]) {
const config = process.env.CLOUDSDK_CONFIG
if (!config) return super._tryGetApplicationCredentialsFromWellKnownFile(options)
// Keep local filesystem helpers lazy for explicitly authenticated Worker callers;
// this ADC-file lookup itself requires a Node-compatible filesystem.
const { existsSync } = await import("node:fs")
const { homedir } = await import("node:os")
const { join } = await import("node:path")
const directory = config === "~" ? homedir() : config.startsWith("~/") ? join(homedir(), config.slice(2)) : config
const file = join(directory, "application_default_credentials.json")
if (!existsSync(file)) return null
return this._getApplicationCredentialsFromFilePath(file, options)
}
}
return new CloudSDKAuth({ projectId: project, scopes: [SCOPE] }).getClient()
}
export type OAuthOptions =
| { readonly accessToken?: string; readonly auth?: never }
| { readonly accessToken?: never; readonly auth?: Auth.Definition }
@@ -21,12 +46,12 @@ export const project = (value?: string) =>
process.env.GCP_PROJECT ??
process.env.GCLOUD_PROJECT
export const location = (value: string | undefined, fallback: string) =>
export const location = (value: string | undefined) =>
value ??
process.env.GOOGLE_VERTEX_LOCATION ??
process.env.GOOGLE_CLOUD_LOCATION ??
process.env.VERTEX_LOCATION ??
fallback
"global"
export const host = (location: string) => {
if (location === "global") return "aiplatform.googleapis.com"
@@ -43,23 +68,25 @@ export const requireProject = (value: string | undefined) => {
})
}
export const apiKey = (input: ApiKeyOptions) => {
export const apiKey = (input: ApiKeyOptions & { readonly project?: string; readonly location?: string }) => {
if (input.apiKey !== undefined && (input.accessToken !== undefined || input.auth !== undefined))
throw new ProviderConfigurationError({
provider: id,
message: "Google Vertex apiKey cannot be combined with accessToken or auth",
})
if (input.accessToken !== undefined || input.auth !== undefined) return undefined
return input.apiKey ?? process.env.GOOGLE_VERTEX_API_KEY
if (input.apiKey !== undefined) return input.apiKey
// Same precedence as Google's Gen AI SDK: an explicit project or location selects Google credentials,
// so an ambient API key cannot replace them.
if (input.project !== undefined || input.location !== undefined) return undefined
return process.env.GOOGLE_VERTEX_API_KEY
}
const adc = (project?: string) => {
let client: Promise<AnyAuthClient> | undefined
const loadClient = () => {
if (client) return client
client = import("google-auth-library").then(({ GoogleAuth }) =>
new GoogleAuth({ projectId: project, scopes: [SCOPE] }).getClient(),
)
client = loadADCClient(project)
return client
}
return Auth.effect(
+3 -3
View File
@@ -91,7 +91,7 @@ const configuredRoute = (input: Config, modelID: string | ModelID) => {
provider: id,
message: "Google Vertex tuned models do not support Express Mode API keys",
})
const location = GoogleVertexShared.location(inputLocation, "us-central1")
const location = GoogleVertexShared.location(inputLocation)
const project = GoogleVertexShared.project(inputProject)
const endpoint =
baseURL ??
@@ -130,8 +130,8 @@ export const model: ProviderPackage.Definition<Settings, GeminiProviderOptionsIn
return configure({
...(apiKey === undefined ? { accessToken: accessToken } : { apiKey: apiKey }),
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
location,
project,
providerOptions,
@@ -0,0 +1,2 @@
export { model } from "../google-vertex-interactions.js"
export type { Settings } from "../google-vertex-interactions.js"
@@ -0,0 +1,2 @@
export { model } from "../google-vertex-mistral.js"
export type { Settings } from "../google-vertex-mistral.js"
+2 -2
View File
@@ -76,8 +76,8 @@ export const model: ProviderPackage.Definition<Settings, Gemini.ProviderOptionsI
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
@@ -15,7 +15,7 @@ export const model: ProviderPackage.Definition<Settings, GoogleInteractions.Prov
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).interactions(modelID)
+2 -2
View File
@@ -110,8 +110,8 @@ export const model: ProviderPackage.Definition<Settings, ProviderOptions>["model
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
+2
View File
@@ -22,7 +22,9 @@ export * as Fireworks from "./fireworks.js"
export * as Google from "./google.js"
export * as GoogleVertex from "./google-vertex.js"
export * as GoogleVertexChat from "./google-vertex-chat.js"
export * as GoogleVertexInteractions from "./google-vertex-interactions.js"
export * as GoogleVertexMessages from "./google-vertex-messages.js"
export * as GoogleVertexMistral from "./google-vertex-mistral.js"
export * as GoogleVertexResponses from "./google-vertex-responses.js"
export * as Groq from "./groq.js"
export * as Meta from "./meta.js"
+1 -1
View File
@@ -169,7 +169,7 @@ function fromSettings({ apiKey, baseURL, body, headers, ...providerOptions }: Se
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
})
}
+1 -1
View File
@@ -133,7 +133,7 @@ export const model: ProviderPackage.Definition<Settings<MessagesOptionsInput>, M
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
+1 -1
View File
@@ -11,6 +11,6 @@ export const model: ProviderPackage.Definition<Settings, MiniMax.ChatOptionsInpu
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).chat(modelID)
@@ -11,6 +11,6 @@ export const model: ProviderPackage.Definition<Settings, MiniMax.ResponsesOption
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).responses(modelID)
+2 -2
View File
@@ -46,8 +46,8 @@ export const model: ProviderPackage.Definition<Settings, ProviderOptions>["model
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
+1 -1
View File
@@ -142,7 +142,7 @@ export const model: ProviderPackage.Definition<Settings, ChatOptionsInput>["mode
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
@@ -11,6 +11,6 @@ export const model: ProviderPackage.Definition<Settings, Moonshot.MessagesOption
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).messages(modelID)
@@ -11,6 +11,6 @@ export const model: ProviderPackage.Definition<Settings, Moonshot.ResponsesOptio
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).responses(modelID)
@@ -53,8 +53,8 @@ export const model: ProviderPackage.Definition<Settings, OpenResponsesProviderOp
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
provider,
providerOptions,
}).model(modelID)
@@ -52,8 +52,8 @@ export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsIn
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
provider,
providerOptions,
}).model(modelID)
+2 -2
View File
@@ -152,9 +152,9 @@ const config = ({
apiKey,
baseURL,
headers: Object.keys(headers).length === 0 ? undefined : headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
queryParams: queryParams === undefined ? undefined : { ...queryParams },
queryParams,
}
}
+1 -1
View File
@@ -212,6 +212,6 @@ export const model: ProviderPackage.Definition<Settings, OpenRouterProviderOptio
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
+2 -2
View File
@@ -59,7 +59,7 @@ export const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsIn
configure({
apiKey,
baseURL,
headers: headers === undefined ? undefined : { ...headers },
http: body === undefined ? undefined : { body: { ...body } },
headers,
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
+1 -1
View File
@@ -55,7 +55,7 @@ export const model: ProviderPackage.Definition<Settings, ChatOptionsInput>["mode
baseURL,
queryParams,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { Headers, HttpClientRequest } from "effect/http"
import {
EvaluationAnswer,
EvaluationInput,
@@ -374,7 +374,7 @@ function fromSettings({ apiKey, baseURL, headers, body, ...providerOptions }: Se
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
})
}
+1 -1
View File
@@ -112,7 +112,7 @@ export const model: ProviderPackage.Definition<
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
export const responses = provider.responses
+1 -1
View File
@@ -88,7 +88,7 @@ export const model: ProviderPackage.Definition<Settings, ChatOptionsInput>["mode
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
@@ -11,6 +11,6 @@ export const model: ProviderPackage.Definition<Settings, ZAICodingPlan.MessagesO
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).messages(modelID)
@@ -11,6 +11,6 @@ export const model: ProviderPackage.Definition<Settings, ZAICodingPlan.Responses
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).responses(modelID)
+1 -1
View File
@@ -73,7 +73,7 @@ export const model: ProviderPackage.Definition<Settings, ChatOptionsInput>["mode
apiKey,
baseURL,
headers,
http: body === undefined ? undefined : { body: { ...body } },
http: body === undefined ? undefined : { body },
providerOptions,
}).model(modelID)
+2 -2
View File
@@ -1,5 +1,5 @@
import { Config, Effect, Option, Redacted } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { AuthenticationError, AIError, type HttpOptions } from "../schema/index.js"
export class MissingCredentialError extends Error {
@@ -95,7 +95,7 @@ export const optional = (secret: Secret | undefined, source = "optional value")
export const config = (name: string) =>
credential(
Effect.gen(function* () {
const secret = yield* Config.option(Config.redacted(name))
const secret = yield* Config.option(Config.Redacted(name))
if (Option.isSome(secret) && Redacted.value(secret.value) !== "") return secret.value
return yield* Effect.fail(new MissingCredentialError(name, `${name} is not set`))
}),
+1 -1
View File
@@ -1,5 +1,5 @@
import { Context, type Effect } from "effect"
import type { HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import type { HttpClientRequest, HttpClientResponse } from "effect/http"
import type { AIError } from "../schema/errors.js"
// The service tag lives in its own leaf module so `Media.Asset` (imported by the schema layer) can require the
+1 -1
View File
@@ -6,7 +6,7 @@ import {
HttpClientError,
HttpClientRequest,
HttpClientResponse,
} from "effect/unstable/http"
} from "effect/http"
import { HttpContext, HttpRateLimitDetails, AIError, TransportError } from "../schema/index.js"
import { classifyProviderFailure, providerErrorMessage } from "../provider-error.js"
import { Service, type HttpMiddleware, type Interface } from "./executor-service.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Stream } from "effect"
import { makeParser } from "effect/unstable/encoding/Sse"
import { makeParser } from "effect/encoding/Sse"
import { AIError, InvalidProviderOutputError } from "../schema/index.js"
/**
+1 -1
View File
@@ -1,5 +1,5 @@
import { Clock, Duration, Effect, Schema, type Stream } from "effect"
import { HttpClientResponse } from "effect/unstable/http"
import { HttpClientResponse } from "effect/http"
import type { Snapshot, Status } from "../generation.js"
import { Media } from "../media.js"
import type { AuthInput } from "./auth.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Duration, Effect, Schedule, Schema, Stream } from "effect"
import { Headers, HttpClientRequest, type HttpClientResponse } from "effect/unstable/http"
import { Headers, HttpClientRequest, type HttpClientResponse } from "effect/http"
import { Auth, type AuthInput } from "./auth.js"
import { Endpoint } from "./endpoint.js"
import { RequestExecutorService, type Interface } from "./executor-service.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Clock, Duration, Effect, Stream } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { Headers, HttpClientRequest } from "effect/http"
import { Auth } from "../auth.js"
import { render as renderEndpoint } from "../endpoint.js"
import { Framing } from "../framing.js"
@@ -1,5 +1,5 @@
import type { Effect, Scope, Stream } from "effect"
import type { Headers } from "effect/unstable/http"
import type { Headers } from "effect/http"
import type { AIError, HttpContext } from "../../schema/index.js"
export interface WebSocketChannelExecutor {
+12 -22
View File
@@ -1,6 +1,6 @@
import { Cause, Effect, Queue, Stream } from "effect"
import { Headers } from "effect/unstable/http"
import { Socket } from "effect/unstable/socket"
import { Headers } from "effect/http"
import { Socket } from "effect/socket"
import {
AIError,
AIErrorReason,
@@ -33,11 +33,6 @@ export interface WebSocketConnector {
readonly open: (input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError>
}
type WebSocketConstructorWithHeaders = (
url: string,
options?: { readonly headers?: Headers.Headers },
) => globalThis.WebSocket
const MAX_FRAME_BYTES = 16 * 1024 * 1024
const transportError = (
message: string,
@@ -81,9 +76,9 @@ const annotateTransportError = (
})
: error
const eventMessage = (event: Event) => {
const eventMessage = (event: Socket.WebSocketEvent) => {
if ("message" in event && typeof event.message === "string") return event.message
return event.type
return event.type ?? "error"
}
const binaryMessage = (data: unknown) => {
@@ -93,7 +88,7 @@ const binaryMessage = (data: unknown) => {
return undefined
}
const waitOpen = (ws: globalThis.WebSocket, input: WebSocketRequest) => {
const waitOpen = (ws: Socket.WebSocketLike, input: WebSocketRequest) => {
if (ws.readyState === globalThis.WebSocket.OPEN) return Effect.void
if (ws.readyState === globalThis.WebSocket.CLOSING || ws.readyState === globalThis.WebSocket.CLOSED) {
return Effect.fail(
@@ -125,7 +120,7 @@ const waitOpen = (ws: globalThis.WebSocket, input: WebSocketRequest) => {
cleanup()
resume(Effect.void)
}
const onError = (event: Event) => {
const onError = (event: Socket.WebSocketEvent) => {
cleanup()
resume(
Effect.fail(
@@ -139,7 +134,7 @@ const waitOpen = (ws: globalThis.WebSocket, input: WebSocketRequest) => {
),
)
}
const onClose = (event: CloseEvent) => {
const onClose = (event: Socket.WebSocketEvent) => {
cleanup()
resume(
Effect.fail(
@@ -191,12 +186,7 @@ export const open = (input: WebSocketRequest) =>
Effect.gen(function* () {
const constructor = yield* Socket.WebSocketConstructor
const ws = yield* Effect.try({
try: () =>
// Platform implementations may extend Effect's browser-compatible constructor with handshake options.
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion
(constructor as unknown as WebSocketConstructorWithHeaders)(input.url, {
headers: input.headers,
}),
try: () => constructor(input.url, { headers: input.headers }),
catch: (error) =>
transportError(error instanceof Error ? error.message : "Failed to construct WebSocket", {
cause: error,
@@ -210,7 +200,7 @@ export const open = (input: WebSocketRequest) =>
})
export const fromWebSocket = (
ws: globalThis.WebSocket,
ws: Socket.WebSocketLike,
input: WebSocketRequest,
): Effect.Effect<WebSocketConnection, AIError> =>
Effect.gen(function* () {
@@ -243,7 +233,7 @@ export const fromWebSocket = (
Queue.offerUnsafe(messages, message)
}
const onMessage = (event: MessageEvent) => {
const onMessage = (event: Socket.WebSocketEvent) => {
if (typeof event.data === "string") return offer(event.data)
const binary = binaryMessage(event.data)
if (binary) return offer(binary)
@@ -260,7 +250,7 @@ export const fromWebSocket = (
),
)
}
const onError = (event: Event) => {
const onError = (event: Socket.WebSocketEvent) => {
Queue.failCauseUnsafe(
messages,
Cause.fail(
@@ -274,7 +264,7 @@ export const fromWebSocket = (
),
)
}
const onClose = (event: CloseEvent) => {
const onClose = (event: Socket.WebSocketEvent) => {
Queue.failCauseUnsafe(
messages,
Cause.fail(
+9 -8
View File
@@ -1,4 +1,5 @@
import { Schema } from "effect"
import { withStatics } from "@opencode/schema/schema"
import { Tool } from "@opencode/schema/tool"
import {
CacheHint,
@@ -28,7 +29,7 @@ export type SystemPart = Schema.Schema.Type<typeof systemPartSchema>
const makeSystemPart = (text: string): SystemPart => ({ type: "text", text })
export const SystemPart = Object.assign(systemPartSchema, {
export const SystemPart = withStatics(systemPartSchema, {
make: makeSystemPart,
content: (input?: string | SystemPart | ReadonlyArray<SystemPart>) => {
if (input === undefined) return []
@@ -76,7 +77,7 @@ const toolResultValueSchema = Schema.Union([
export type ToolResultValue = Schema.Schema.Type<typeof toolResultValueSchema>
const isToolResultValue = Schema.is(toolResultValueSchema)
export const ToolResultValue = Object.assign(toolResultValueSchema, {
export const ToolResultValue = withStatics(toolResultValueSchema, {
is: isToolResultValue,
make: (value: unknown, type: ToolResultValue["type"] = "json"): ToolResultValue => {
if (isToolResultValue(value)) return value
@@ -90,7 +91,7 @@ export interface ToolOutput {
readonly content: ReadonlyArray<Tool.Content>
}
export const ToolOutput = Object.assign(
export const ToolOutput = withStatics(
Schema.Struct({
structured: Schema.Unknown,
content: Schema.Array(Tool.Content),
@@ -127,7 +128,7 @@ const toolResultText = (value: unknown) => {
}
}
export const ToolCallPart = Object.assign(
export const ToolCallPart = withStatics(
Schema.Struct({
type: Schema.Literal("tool-call"),
id: Schema.String,
@@ -145,7 +146,7 @@ export const ToolCallPart = Object.assign(
)
export type ToolCallPart = Schema.Schema.Type<typeof ToolCallPart>
export const ToolResultPart = Object.assign(
export const ToolResultPart = withStatics(
Schema.Struct({
type: Schema.Literal("tool-result"),
id: Schema.String,
@@ -209,7 +210,7 @@ const compactionPartSchema = Schema.Struct({
)
.annotate({ identifier: "LLM.Content.Compaction" })
export type CompactionPart = typeof compactionPartSchema.Type
export const CompactionPart = Object.assign(compactionPartSchema, {
export const CompactionPart = withStatics(compactionPartSchema, {
make: (input: Omit<CompactionPart, "type" | "encrypted" | "text"> & CompactionContent): CompactionPart =>
Schema.decodeUnknownSync(compactionPartSchema)({ type: "compaction", ...input }),
})
@@ -326,7 +327,7 @@ export type ToolNamespaceEntryInput = ToolNamespaceInput & { readonly type: "nam
export const ToolNamespace: Schema.Codec<ToolNamespace> & {
readonly make: (input: ToolNamespace | ToolNamespaceInput) => ToolNamespace
} = Object.assign(
} = withStatics(
Schema.Struct({
type: Schema.Literal("namespace"),
name: Schema.String,
@@ -346,7 +347,7 @@ export type ToolEntry = ToolDefinition | ToolNamespace
export type ToolEntryInput = ToolDefinition.Input | ToolNamespaceEntryInput
export const ToolEntry: Schema.Codec<ToolEntry> & {
readonly make: (input: ToolEntryInput) => ToolEntry
} = Object.assign(
} = withStatics(
Schema.Union([ToolDefinition, ToolNamespace]).pipe(
Schema.toTaggedUnion("type"),
Schema.annotate({ identifier: "LLM.ToolEntry" }),
+21 -2
View File
@@ -6,6 +6,7 @@ import type {
ToolOutput as ToolOutputType,
} from "./schema/index.js"
import { ToolDefinition, ToolFailure, ToolOutput } from "./schema/index.js"
import { isRecord } from "./utils/record.js"
/**
* Schema constraint for tool parameters / success values: no decoding or
@@ -200,7 +201,7 @@ export function make(config: TypedToolConfig | DynamicToolConfig): AnyTool {
_definition: new ToolDefinition({
name: "",
description: config.description,
inputSchema: toJsonSchema(config.parameters),
inputSchema: emptyInputJsonSchema(config.parameters) ?? toJsonSchema(config.parameters),
outputSchema: toJsonSchema(config.success),
}),
}
@@ -230,8 +231,26 @@ export const toDefinitions = (tools: Tools): ReadonlyArray<ToolDefinitionClass>
}),
)
/**
* Describes a no-argument Effect tool input as an empty object, or returns
* `undefined` when the input takes arguments.
*
* Effect emits `{ not: { type: "null" } }` only for an encoded struct with no
* properties or index signatures, but providers require tool parameters to
* describe an object. Annotations are kept; any other keyword, such as a
* check's `minProperties`, leaves the schema as Effect emitted it.
*/
export const emptyInputJsonSchema = (schema: Schema.Top): JsonSchema.JsonSchema | undefined => {
const { not, ...rest } = Schema.toJsonSchemaDocument(schema, { referencePolicy: () => undefined }).schema
if (!isRecord(not) || not.type !== "null" || Object.keys(not).length !== 1) return undefined
if (!Object.keys(rest).every((key) => annotationKeywords.has(key))) return undefined
return { ...rest, type: "object", properties: {}, additionalProperties: false }
}
const annotationKeywords = new Set(["title", "description", "default", "examples", "readOnly", "writeOnly"])
const toJsonSchema = (schema: Schema.Top): JsonSchema.JsonSchema => {
const document = Schema.toJsonSchemaDocument(schema)
const document = Schema.toJsonSchemaDocument(schema, { onExcessProperty: "error" })
if (Object.keys(document.definitions).length === 0) return document.schema
return { ...document.schema, $defs: document.definitions }
}
+1 -1
View File
@@ -29,7 +29,7 @@ type LanguageModel = {
declare const auth: Auth.Definition
declare const optionalAuthModel: LanguageModelFactory<BaseOptions, "optional", LanguageModel>
declare const requiredAuthModel: LanguageModelFactory<BaseOptions, "required", LanguageModel>
const configApiKey = Config.redacted("OPENAI_API_KEY")
const configApiKey = Config.Redacted("OPENAI_API_KEY")
OpenAIChat.route.model({ id: "gpt-4.1-mini" })
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { ConfigProvider, Effect } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { LLM } from "../src/index.js"
import { Auth } from "../src/route/auth.js"
import * as OpenAIChat from "../src/protocols/openai-chat.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { expect } from "bun:test"
import { Effect } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { FetchHttpClient } from "effect/http"
import { LLM, LLMRequest, Message } from "../src/index.js"
import { LLMClient } from "../src/route/client.js"
import { OpenAI } from "../src/providers.js"
Loaded 100 of 686 files, more files were not shown because too many files have changed in this diff. Show more