Compare commits

...
Author SHA1 Message Date
GitHub Action 66c2caf07a chore: format code
format / format (push) Has been cancelled
test / test (push) Has been cancelled
Update Nix Hashes / update (push) Has been cancelled
2025-12-01 14:43:56 +00:00
blacksmith-sh[bot]andGitHub 014d9c27f3 Migrate workflows to Blacksmith 2025-12-01 14:43:21 +00:00
Dax Raad 0ddfdb55d3 log fetch time 2025-12-01 09:35:08 -05:00
GitHub Actionandopencode fc439455a7 chore: format code 2025-12-01 14:23:36 +00:00
opencode c0fc02769b release: v1.0.125
Update Nix Hashes / update (push) Has been cancelled
2025-12-01 14:23:35 +00:00
Dax Raad 068ac68496 docs: add rebase instructions to commit command 2025-12-01 09:14:54 -05:00
Dax Raad 7da6a22df2 core: close SSE stream when instance is disposed 2025-12-01 09:14:00 -05:00
Sebastian Herrlinger e37aeb6e6a no conceal for write tool output 2025-12-01 15:12:47 +01:00
GitHub Action eeb0d2b8e0 chore: format code 2025-12-01 13:27:29 +00:00
adamelmore bf8c866bf7 chore: otf fonts 2025-12-01 07:20:53 -06:00
GitHub Action 75354b0002 ignore: update download stats 2025-12-01 2025-12-01 12:08:56 +00:00
opencode 4eb4d97d51 release: v1.0.124
Update Nix Hashes / update (push) Has been cancelled
2025-12-01 09:20:29 +00:00
Aiden Cline b1b82977ec tweak: better err msgs 2025-12-01 01:33:32 -06:00
Aiden Cline f6262460ff fix: drop absolute flag to resolve issues when using older git versions 2025-12-01 00:23:19 -06:00
GitHub Action 560a610384 chore: format code 2025-12-01 02:53:41 +00:00
Aiden Cline 0308b2ff98 bump anthropic plugin to fix header issue 2025-11-30 20:52:50 -06:00
opencode 5b92d49be7 release: v1.0.123
Update Nix Hashes / update (push) Has been cancelled
2025-12-01 01:07:50 +00:00
Github Action 0386d0ae09 Update Nix flake.lock and hashes 2025-12-01 01:00:23 +00:00
Sebastian Herrlinger 28bec57e1d bump opentui to v0.1.54, removing the cc flicker feature, fixing diffs and input event handling 2025-12-01 01:57:55 +01:00
aaa31f02af tweak: compaction prompt (#4838)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-30 14:31:57 -06:00
Justin VogtandGitHub ff609a52c1 docs: explore subagent (#4893) 2025-11-30 14:04:27 -06:00
GitHub Action 1e30793f0a ignore: update download stats 2025-11-30 2025-11-30 12:04:23 +00:00
Adam 5268eb479d feat(share): split diffs on wide screens 2025-11-30 05:29:07 -06:00
Aiden Cline a4eba2e6e9 tweak: plan prompt 2025-11-30 01:03:35 -06:00
Aiden Cline 0f30115205 chore: cleanup 2025-11-30 00:32:43 -06:00
opencode-agent[bot]GitHubopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-node
ae500ea01d Added "Open docs" command to palette (#4915)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2025-11-29 23:49:45 -06:00
Justin VogtandGitHub 087479d459 docs: Move tip to correct tool (#4891) 2025-11-29 22:03:20 -06:00
6e2379a28c get codex working in copilot (#4914)
Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2025-11-29 21:47:29 -06:00
opencode 262fa184fd release: v1.0.122
Update Nix Hashes / update (push) Has been cancelled
2025-11-30 00:35:34 +00:00
Github Action 9a7e1c154d Update Nix flake.lock and hashes 2025-11-29 23:56:14 +00:00
Sebastian Herrlinger 5bf9193dfa bump opentui to v0.1.53, fixing an event listener leak in the line number renderable (perf) 2025-11-30 00:52:32 +01:00
Sebastian Herrlinger 180fb3f39d tweak spinner to be bg independent 2025-11-30 00:50:30 +01:00
GitHub Action 7e6b7314f4 chore: format code 2025-11-29 21:41:00 +00:00
Dax Raad a262508fb8 core: clarify general agent can execute multiple parallel tasks 2025-11-29 16:40:15 -05:00
Dax Raadandopencode 80ff24b65a ci: allow snapshot builds from any branch starting with snapshot- prefix 2025-11-29 21:30:38 +00:00
opencode 012aa67e42 release: v1.0.121
Update Nix Hashes / update (push) Has been cancelled
2025-11-29 21:30:38 +00:00
Github Action 0a1f12a583 Update Nix flake.lock and hashes 2025-11-29 21:20:24 +00:00
Sebastian Herrlinger f17dc812d0 upgrade opentui to v0.1.52, fixing #4906 - key repeat handling 2025-11-29 22:18:08 +01:00
1854d85ccc fix(tui): add missing fg color to permission keybind hints (#4899)
Co-authored-by: Github Action <action@github.com>
2025-11-29 14:22:20 -06:00
Adam 2c4d1fb8b4 chore: cleanup duplicate markup 2025-11-29 06:18:51 -06:00
GitHub Action d8fa7cf65d ignore: update download stats 2025-11-29 2025-11-29 12:04:12 +00:00
Dorian KarterandGitHub 7d8d360138 fix: minor ui bug for transparent backgrounds (#4886) 2025-11-28 23:58:44 -06:00
Dax Raad d80880350d core: improve explore agent description to clarify tool availability 2025-11-28 21:47:45 -05:00
GitHub Action b693ed0dbd chore: format code 2025-11-29 02:44:40 +00:00
Dax Raad 83f961a7c2 Merge remote-tracking branch 'origin/dev' into dev 2025-11-28 21:44:02 -05:00
Dax Raad a093917db1 core: update generated types to include explore agent configuration 2025-11-28 21:43:56 -05:00
opencode 52716db649 release: v1.0.120
Update Nix Hashes / update (push) Has been cancelled
2025-11-29 02:34:23 +00:00
Dax Raad 9ca4b464ea tui: improve task display in session view to show tool names and completion status 2025-11-28 21:19:15 -05:00
Dax Raad 204a31b6bb Merge remote-tracking branch 'origin/dev' into dev 2025-11-28 21:13:13 -05:00
Dax Raad 813d287a09 core: add explore agent for fast codebase navigation and improve task UI display 2025-11-28 21:13:07 -05:00
Adam 4dd9f33eba fix: diffs double rendering when CSR'd 2025-11-28 20:08:50 -06:00
Adam 5953378a12 fix: theme-color value 2025-11-28 19:57:01 -06:00
Github Action b419eed295 Update Nix flake.lock and hashes 2025-11-29 01:55:02 +00:00
Sebastian Herrlinger 52deb7f352 opentui diffs 2025-11-29 02:52:17 +01:00
Dax Raad a4f3aecbaa ignore 2025-11-28 20:29:58 -05:00
GitHub Action 49ff6a852a chore: format code 2025-11-28 22:43:33 +00:00
Justin VogtandGitHub 7f537d2e98 docs: Add tip to tools about subagents re todos (#4875) 2025-11-28 15:49:24 -06:00
Aiden Cline 753443b16f ci: add community contributors to the changelog 2025-11-28 15:44:21 -06:00
Ariane EmoryGitHubGitHub Actionopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-nodeAiden Cline
33c63be980 feat: persist thinking blocks display to KV and indicate its current display state in the command_list (resolves #4582) (#4810)
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-28 15:01:05 -06:00
Aiden Cline b6efca42b4 ci: use haiku 2025-11-28 14:41:32 -06:00
Github Action fa6eadc39a Update Nix flake.lock and hashes 2025-11-28 20:32:46 +00:00
Aiden Cline 8789acefa6 bump openrouter & google ai sdk packages 2025-11-28 14:30:22 -06:00
Aiden Cline 0e280017e6 Revert "fix: title gen when first msg(s) are shell invocations (#4874)"
This reverts commit 17e8322c29.
2025-11-28 12:18:02 -06:00
17e8322c29 fix: title gen when first msg(s) are shell invocations (#4874)
Co-authored-by: GitHub Action <action@github.com>
2025-11-28 11:55:22 -06:00
Sergio GarciaGitHubopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
96eda740cd docs: formatter: false and lsp: false (#4833)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
2025-11-28 11:22:48 -06:00
fa84612357 tweak: gemini retry message to not be explicitly about gemini 3 (#4864)
Co-authored-by: jesuso <j.ochoa@norteconecta.net>
2025-11-28 11:14:53 -06:00
Dax Raad cf1f63eda3 ci stuff 2025-11-28 10:59:52 -05:00
Dax Raad 9704f5ce89 add otui-diffs 2025-11-28 10:59:07 -05:00
GitHub Action 0eaec2af82 ignore: update download stats 2025-11-28 2025-11-28 12:04:42 +00:00
Adam 398d35dc97 fix: theme-color value 2025-11-28 05:48:07 -06:00
Adam 5efeaae093 fix: desktop and share layouts 2025-11-28 05:35:35 -06:00
Adam cb2dd34a5e fix: unified diff as default 2025-11-28 05:35:35 -06:00
ChristophandGitHub 7112a706b8 core: add built-in Dart LSP server and formatter (#4841) 2025-11-28 00:33:45 -06:00
Github Action 025a47d01f Update Nix flake.lock and hashes 2025-11-28 06:27:49 +00:00
DSandGitHub 13f89fdb8f fix: filter empty messages in toModelMessage (#4811) 2025-11-28 00:26:48 -06:00
Aiden Cline cc78d50ef6 bump anthropic package 2025-11-28 00:25:16 -06:00
Adam a8985b1849 fix(desktop): layout 2025-11-27 20:15:49 -06:00
Adam 6a1552f65c fix: unwrap solid store part 2025-11-27 20:15:49 -06:00
Dax Raad 776091cc23 ci: add bun version check to pre-push hook to ensure version consistency 2025-11-27 15:50:23 -05:00
Dax Raad f385524f48 fix lock 2025-11-27 15:48:16 -05:00
Dax Raad 350982e636 tui: simplify model dialog ordering logic to reduce complexity 2025-11-27 14:38:51 -05:00
Dax Raad 5854455815 tui: improve provider dialog text clarity for better user guidance 2025-11-27 14:09:53 -05:00
Dax Raad 9ecaf618db tui: fix provider sorting to prioritize recommended options 2025-11-27 13:54:42 -05:00
Dax Raad 95b667d21e tui: remove cancel keybind hint from prompt dialog to simplify UI 2025-11-27 13:48:34 -05:00
Dax Raad a0b689c140 tui: hide favorite keybind in model dialog when disconnected to prevent errors 2025-11-27 13:42:36 -05:00
GitHub Action ea52ed41be chore: format code 2025-11-27 17:15:41 +00:00
Jay V 5a50d54fda ignore: lock 2025-11-27 12:14:50 -05:00
Jay V 35d118b0c4 ignore: add reply-to support for enterprise form emails 2025-11-27 12:12:44 -05:00
ea7c213f5d nix: fix workflow failing on PRs (#4820)
Co-authored-by: Github Action <action@github.com>
2025-11-27 10:05:51 -06:00
Frank 70dd6dd394 doc: slashing kimi k2 thinking price 2025-11-27 09:58:57 -05:00
GitHub Action 049510afbd ignore: update download stats 2025-11-27 2025-11-27 12:04:30 +00:00
Adam c120447fd0 fix: desktop layout and scroll gutters 2025-11-27 05:41:50 -06:00
Adam feb1f36126 fix: session turn margins 2025-11-27 05:25:39 -06:00
GitHub Action d6ef47bb2d chore: format code 2025-11-27 11:06:46 +00:00
Adam 50fd416d49 fix: simpler sanitize 2025-11-27 05:05:55 -06:00
opencode aef6904247 release: v1.0.119
Update Nix Hashes / update (push) Has been cancelled
2025-11-27 03:15:40 +00:00
Dax Raad 0bf40faf95 core: prevent codesearch and websearch tools when webfetch permission is denied 2025-11-26 22:08:50 -05:00
GitHub Action c90987c4b0 chore: format code 2025-11-27 03:08:33 +00:00
Dax Raad 0e08655407 core: prevent external diff tools from interfering with snapshot generation 2025-11-26 22:07:51 -05:00
114 changed files with 4101 additions and 512 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ on:
jobs:
auto-label:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
issues: write
+1 -1
View File
@@ -11,7 +11,7 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
deploy:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v3
+1 -1
View File
@@ -6,7 +6,7 @@ on:
jobs:
check-duplicates:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
issues: write
+1 -1
View File
@@ -10,7 +10,7 @@ on:
workflow_dispatch:
jobs:
format:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
steps:
+1 -1
View File
@@ -6,7 +6,7 @@ on:
jobs:
notify:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Send nicely-formatted embed to Discord
uses: SethCohen/github-releases-to-discord@v1
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
startsWith(github.event.comment.body, '/oc') ||
contains(github.event.comment.body, ' /opencode') ||
startsWith(github.event.comment.body, '/opencode')
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
id-token: write
contents: read
@@ -30,4 +30,4 @@ jobs:
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
with:
model: opencode/glm-4.6
model: opencode/claude-haiku-4-5
+1 -1
View File
@@ -14,7 +14,7 @@ permissions:
jobs:
publish:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v3
with:
+1 -1
View File
@@ -13,7 +13,7 @@ permissions:
jobs:
publish:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v3
with:
+1 -1
View File
@@ -25,7 +25,7 @@ permissions:
jobs:
publish:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v3
with:
+4 -1
View File
@@ -1,17 +1,20 @@
name: snapshot
on:
workflow_dispatch:
push:
branches:
- dev
- test-bedrock
- v0
- otui-diffs
- snapshot-*
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
publish:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v3
with:
+1 -1
View File
@@ -7,7 +7,7 @@ on:
jobs:
stats:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
+1 -1
View File
@@ -8,7 +8,7 @@ on:
jobs:
zed:
name: Release Zed Extension
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v4
with:
+1 -1
View File
@@ -10,7 +10,7 @@ on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v4
+1 -1
View File
@@ -7,7 +7,7 @@ on:
jobs:
typecheck:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v4
+3 -1
View File
@@ -18,7 +18,8 @@ on:
jobs:
update:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: blacksmith-4vcpu-ubuntu-2404
env:
SYSTEM: x86_64-linux
@@ -29,6 +30,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
ref: ${{ github.head_ref || github.ref_name }}
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
- name: Setup Nix
uses: DeterminateSystems/nix-installer-action@v20
+7
View File
@@ -1,2 +1,9 @@
#!/bin/sh
# Check if bun version matches package.json
EXPECTED_VERSION=$(grep '"packageManager"' package.json | sed 's/.*"bun@\([^"]*\)".*/\1/')
CURRENT_VERSION=$(bun --version)
if [ "$CURRENT_VERSION" != "$EXPECTED_VERSION" ]; then
echo "Error: Bun version $CURRENT_VERSION does not match expected version $EXPECTED_VERSION from package.json"
exit 1
fi
bun typecheck
+3
View File
@@ -21,3 +21,6 @@ WHAT was done.
do not do generic messages like "improved agent experience" be very specific
about what user facing changes were made
if there are changes do a git pull --rebase
if there are conflicts DO NOT FIX THEM. notify me and I will fix them
+7
View File
@@ -16,5 +16,12 @@
"type": "remote",
"url": "https://mcp.exa.ai/mcp",
},
"morph": {
"type": "local",
"command": ["bunx", "@morphllm/morphmcp"],
"environment": {
"ENABLED_TOOLS": "warp_grep",
},
},
},
}
+5
View File
@@ -152,3 +152,8 @@
| 2025-11-24 | 856,733 (+10,124) | 804,033 (+8,964) | 1,660,766 (+19,088) |
| 2025-11-25 | 869,423 (+12,690) | 817,339 (+13,306) | 1,686,762 (+25,996) |
| 2025-11-26 | 881,414 (+11,991) | 832,518 (+15,179) | 1,713,932 (+27,170) |
| 2025-11-27 | 893,960 (+12,546) | 846,180 (+13,662) | 1,740,140 (+26,208) |
| 2025-11-28 | 901,741 (+7,781) | 856,482 (+10,302) | 1,758,223 (+18,083) |
| 2025-11-29 | 908,689 (+6,948) | 863,361 (+6,879) | 1,772,050 (+13,827) |
| 2025-11-30 | 916,116 (+7,427) | 870,194 (+6,833) | 1,786,310 (+14,260) |
| 2025-12-01 | 925,898 (+9,782) | 876,500 (+6,306) | 1,802,398 (+16,088) |
+45 -43
View File
@@ -20,7 +20,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -48,7 +48,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -75,7 +75,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -99,7 +99,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -123,7 +123,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -164,7 +164,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -192,7 +192,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@@ -208,7 +208,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.0.118",
"version": "1.0.125",
"bin": {
"opencode": "./bin/opencode",
},
@@ -217,13 +217,15 @@
"@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.5.1",
"@ai-sdk/amazon-bedrock": "3.0.57",
"@ai-sdk/anthropic": "2.0.45",
"@ai-sdk/anthropic": "2.0.50",
"@ai-sdk/azure": "2.0.73",
"@ai-sdk/google": "2.0.42",
"@ai-sdk/google-vertex": "3.0.74",
"@ai-sdk/google": "2.0.44",
"@ai-sdk/google-vertex": "3.0.81",
"@ai-sdk/mcp": "0.0.8",
"@ai-sdk/openai": "2.0.71",
"@ai-sdk/openai-compatible": "1.0.27",
"@ai-sdk/provider": "2.0.0",
"@ai-sdk/provider-utils": "3.0.18",
"@clack/prompts": "1.0.0-alpha.1",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
@@ -235,9 +237,9 @@
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.2.5",
"@opentui/core": "0.1.50",
"@opentui/solid": "0.1.50",
"@openrouter/ai-sdk-provider": "1.2.8",
"@opentui/core": "0.1.54",
"@opentui/solid": "0.1.54",
"@parcel/watcher": "2.5.1",
"@pierre/precision-diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
@@ -256,7 +258,7 @@
"jsonc-parser": "3.3.1",
"minimatch": "10.0.3",
"open": "10.1.2",
"opentui-spinner": "0.0.5",
"opentui-spinner": "0.0.6",
"partial-json": "0.1.7",
"remeda": "catalog:",
"solid-js": "catalog:",
@@ -295,7 +297,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -315,7 +317,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.0.118",
"version": "1.0.125",
"devDependencies": {
"@hey-api/openapi-ts": "0.81.0",
"@tsconfig/node22": "catalog:",
@@ -326,7 +328,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -339,7 +341,7 @@
},
"packages/tauri": {
"name": "@opencode-ai/tauri",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-opener": "^2",
@@ -352,7 +354,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -384,7 +386,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"zod": "catalog:",
},
@@ -394,7 +396,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -491,9 +493,9 @@
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.12", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W+cB1sOWvPcz9qiIsNtD+HxUrBUva2vWv2K1EFukuImX+HA0uZx3EyyOjhYQ9gtf/teqEG80M6OvJ7xx/VLV2A=="],
"@ai-sdk/google": ["@ai-sdk/google@2.0.42", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Jdn+3TZm4iIt62CUjjUoIOshqFIXyzNmUDfkSVV4FcjlSo5+AuhzI1KC7QiNHlqPNejzR6NLIqGJx96VAES34g=="],
"@ai-sdk/google": ["@ai-sdk/google@2.0.44", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-c5dck36FjqiVoeeMJQLTEmUheoURcGTU/nBT6iJu8/nZiKFT/y8pD85KMDRB7RerRYaaQOtslR2d6/5PditiRw=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.74", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.45", "@ai-sdk/google": "2.0.42", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "google-auth-library": "^9.15.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W0375p41RQOheAmy7iJGtuJWQWX/aKkO4sJHf6eIYa3bkz93Cbo1aRG1X7ocyMusLZ3dIaW7x6X9WHD8IHkNfg=="],
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.81", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.50", "@ai-sdk/google": "2.0.44", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18", "google-auth-library": "^9.15.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yrl5Ug0Mqwo9ya45oxczgy2RWgpEA/XQQCSFYP+3NZMQ4yA3Iim1vkOjVCsGaZZ8rjVk395abi1ZMZV0/6rqVA=="],
"@ai-sdk/mcp": ["@ai-sdk/mcp@0.0.8", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "pkce-challenge": "^5.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9y9GuGcZ9/+pMIHfpOCJgZVp+AZMv6TkjX2NVT17SQZvTF2N8LXuCXyoUPyi1PxIxzxl0n463LxxaB2O6olC+Q=="],
@@ -503,7 +505,7 @@
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="],
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="],
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
@@ -1079,27 +1081,27 @@
"@opencode-ai/web": ["@opencode-ai/web@workspace:packages/web"],
"@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.2.5", "", { "dependencies": { "@openrouter/sdk": "^0.1.8" }, "peerDependencies": { "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" } }, "sha512-NrvJFPvdEUo6DYUQIVWPGfhafuZ2PAIX7+CUMKGknv8TcTNVo0TyP1y5SU7Bgjf/Wup9/74UFKUB07icOhVZjQ=="],
"@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.2.8", "", { "dependencies": { "@openrouter/sdk": "^0.1.8" }, "peerDependencies": { "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" } }, "sha512-pQT8AzZBKg9f4bkt4doF486ZlhK0XjKkevrLkiqYgfh1Jplovieu28nK4Y+xy3sF18/mxjqh9/2y6jh01qzLrA=="],
"@openrouter/sdk": ["@openrouter/sdk@0.1.27", "", { "dependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-RH//L10bSmc81q25zAZudiI4kNkLgxF2E+WU42vghp3N6TEvZ6F0jK7uT3tOxkEn91gzmMw9YVmDENy7SJsajQ=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@opentui/core": ["@opentui/core@0.1.50", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.50", "@opentui/core-darwin-x64": "0.1.50", "@opentui/core-linux-arm64": "0.1.50", "@opentui/core-linux-x64": "0.1.50", "@opentui/core-win32-arm64": "0.1.50", "@opentui/core-win32-x64": "0.1.50", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-QhjwT2f8AIQj0gbL/WQ2M93sl2/qp9+Kqxyh4dOhp8z3qnTc5D7J105VrMyeWZW7/P27ubgbFAqqWXrZ4FsuLw=="],
"@opentui/core": ["@opentui/core@0.1.54", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.54", "@opentui/core-darwin-x64": "0.1.54", "@opentui/core-linux-arm64": "0.1.54", "@opentui/core-linux-x64": "0.1.54", "@opentui/core-win32-arm64": "0.1.54", "@opentui/core-win32-x64": "0.1.54", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-NYBVOmAa3JB+bSTxFTUc3Ej3B8Gc364DMsoktVRCYdjQ+AyeMRNdOTj9sMWdDJbXqITK/atrsUXouoE5bL9sWA=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.50", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FKqTDOsZl9TXF7KN2SdZKoRHQNvqKSY27AG3jhKCoiyLGdaNCAsaeBWqAmpnL4E4kMkV3aiQSCrKTrYsaevvOg=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.54", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LyJ2AI9XA3upckiXLaX/P6tB40pB6euZA5MCikyhwcDVew3Z7NpTI0xHp4gUWHwsSTwmx6u851ofgkf7nMBEJg=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.50", "", { "os": "darwin", "cpu": "x64" }, "sha512-GczVNqqpM/HtsgeBB08K6zL1B7oc6Y5G2cMklo06LrYRdDkFdDtY5fNNnJR2/psZWzTrI3M+sLnKWgUGD5CxUQ=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.54", "", { "os": "darwin", "cpu": "x64" }, "sha512-LZAjXR1OoUr8pewBTHqxupdPS4PC4qQCht0HHGEr1+zN7zu5c+kcp3Uop+NeQTYLjHJftiULukzAfLJMXFuDNA=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.50", "", { "os": "linux", "cpu": "arm64" }, "sha512-+CKMhweEXH0tLGM6qqaqk6DyCEmwrTVubTtez/pSM3GgcROSXIBui9TEZpIlPgSCVmjbotGS6eSIg4oU+p9o7w=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.54", "", { "os": "linux", "cpu": "arm64" }, "sha512-AC7qBYc4shY28/eR1BvguvfCjLzZJy0mdvadSdDF0XJJIe5vhWJ9UyKsoyLo7pwrd0iFBf5d7fg4kJcbLbZNWg=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.50", "", { "os": "linux", "cpu": "x64" }, "sha512-yv5KWiMohAK9bsi1gth9DDZDpoJA1EDHexjhThsPT8EH82g13T088dnJZuJWUE9dr1OwTCQG8DyorNxX3ViEGQ=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.54", "", { "os": "linux", "cpu": "x64" }, "sha512-S7BAq4CUV6ZfoXDSClJT75XxVSKPOOuRVYvBSHMOgwkBwLHpJZdz0Z4mzcGBBP1nJQ5Gz92TWPYfYMbZvR1lpw=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.50", "", { "os": "win32", "cpu": "arm64" }, "sha512-6/6pURTRNTLFKF8IhYVi7U+T/HGMeURav9LIYw7yfcOibd0kLMthmemhS0Lzyk5dmtp0T4V4NmRmtlq/fIzyjQ=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.54", "", { "os": "win32", "cpu": "arm64" }, "sha512-K+E9i8t6YfN0Ly9moHefRQfR5GSbQUAsqzfrW4TD4bpJBy5y7EKh1mz8ZdES/RwWOSGUCb+JN7/ZQm0OubbdvQ=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.50", "", { "os": "win32", "cpu": "x64" }, "sha512-EME8GBFq9uCLbH5js8fH7/xY4ZtLIZlt3bkYKT6lPiCNdaf/6ebg+F/ObPXFkJrc8VeV1ql2bXhQ6RLi7izvAA=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.54", "", { "os": "win32", "cpu": "x64" }, "sha512-0KsBPRtcqmPM1AoN2Ez9au6uf7y8631W9tXTWnVZRlI2/G3j1AfGReR6vuwrd8O4JfxFXiYWWPfSxYZvWLSRNg=="],
"@opentui/solid": ["@opentui/solid@0.1.50", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.50", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-q778kp/eksh8UOPSQO2h8h9CGGDqepTf9u2WYTS2HYHRAI2SRtUWpN9L7Euyt3BtG9L/wpsIOHK/ufPhQH1X6A=="],
"@opentui/solid": ["@opentui/solid@0.1.54", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.54", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-rr9moI+W3meoD57t4Flyfw33mRSFIX0FOx+t4T3sLNA3Pz5FGU2n4qyxPc21OkAuc23S+YxuEuwbZebnIaLGWA=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -2969,7 +2971,7 @@
"openid-client": ["openid-client@5.6.4", "", { "dependencies": { "jose": "^4.15.4", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA=="],
"opentui-spinner": ["opentui-spinner@0.0.5", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.1.49", "@opentui/react": "^0.1.49", "@opentui/solid": "^0.1.49", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-abSWzWA7eyuD0PjerAWbBznLmOQn+8xRDaLGCVIs4ctETi2laNFr5KwicYnPXsHZpPc2neV7WtQm+diCEfOhLA=="],
"opentui-spinner": ["opentui-spinner@0.0.6", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.1.49", "@opentui/react": "^0.1.49", "@opentui/solid": "^0.1.49", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-xupLOeVQEAXEvVJCvHkfX6fChDWmJIPHe5jyUrVb8+n4XVTX8mBNhitFfB9v2ZbkC1H2UwPab/ElePHoW37NcA=="],
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
@@ -3737,20 +3739,22 @@
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
"@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.71", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="],
"@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.45", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Ipv62vavDCmrV/oE/lXehL9FzwQuZOnnlhPEftWizx464Wb6lvnBTJx8uhmEYruFSzOWTI95Z33ncZ4tA8E6RQ=="],
"@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="],
"@ai-sdk/mcp/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
"@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw=="],
"@astrojs/cloudflare/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "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-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
"@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="],
@@ -4073,7 +4077,7 @@
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.45", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Ipv62vavDCmrV/oE/lXehL9FzwQuZOnnlhPEftWizx464Wb6lvnBTJx8uhmEYruFSzOWTI95Z33ncZ4tA8E6RQ=="],
"opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.50", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-21PaHfoLmouOXXNINTsZJsMw+wE5oLR2He/1kq/sKokTVKyq7ObGT1LDk6ahwxaz/GoaNaGankMh+EgVcdv2Cw=="],
"opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.71", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="],
@@ -4619,8 +4623,6 @@
"jsonwebtoken/jws/jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="],
"opencode/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
Generated
+3 -3
View File
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1764138170,
"narHash": "sha256-2bCmfCUZyi2yj9FFXYKwsDiaZmizN75cLhI/eWmf3tk=",
"lastModified": 1764527385,
"narHash": "sha256-nA5ywiGKl76atrbdZ5Aucd8SjF/v8ew9b9QsC+MKL14=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "bb813de6d2241bcb1b5af2d3059f560c66329967",
"rev": "23258e03aaa49b3a68597e3e50eb0cbce7e42e9d",
"type": "github"
},
"original": {
+1 -1
View File
@@ -1,3 +1,3 @@
{
"nodeModules": "sha256-dTGBX5mde/hQP36MSFwq3G81OdwpcYRl8bcjLpesbPw="
"nodeModules": "sha256-CrewwFMa3zxKTM/XpnRVQm75FB7Jk1tXX40D+kYt0YE="
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.0.118",
"version": "1.0.125",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
@@ -36,6 +36,7 @@ ${body.email}`.trim()
to: "contact@anoma.ly",
subject: `Enterprise Inquiry from ${body.name}`,
body: emailContent,
replyTo: body.email,
})
return Response.json({ success: true, message: "Form submitted successfully" }, { status: 200 })
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.0.118",
"version": "1.0.125",
"private": true,
"type": "module",
"dependencies": {
+2
View File
@@ -22,6 +22,7 @@ export namespace AWS {
to: z.string(),
subject: z.string(),
body: z.string(),
replyTo: z.string().optional(),
}),
async (input) => {
const res = await createClient().fetch("https://email.us-east-1.amazonaws.com/v2/email/outbound-emails", {
@@ -35,6 +36,7 @@ export namespace AWS {
Destination: {
ToAddresses: [input.to],
},
...(input.replyTo && { ReplyToAddresses: [input.replyTo] }),
Content: {
Simple: {
Subject: {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.0.118",
"version": "1.0.125",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.0.118",
"version": "1.0.125",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+2 -1
View File
@@ -9,7 +9,8 @@
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#000000" />
<meta name="theme-color" content="#F8F7F7" />
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
</head>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/desktop",
"version": "1.0.118",
"version": "1.0.125",
"description": "",
"type": "module",
"scripts": {
+29 -35
View File
@@ -12,7 +12,7 @@ import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
import { Tabs } from "@opencode-ai/ui/tabs"
import { Code } from "@opencode-ai/ui/code"
import { SessionTurn } from "@opencode-ai/ui/session-turn"
import { MessageNav } from "@opencode-ai/ui/message-nav"
import { SessionMessageRail } from "@opencode-ai/ui/session-message-rail"
import { SessionReview } from "@opencode-ai/ui/session-review"
import { SelectDialog } from "@opencode-ai/ui/select-dialog"
import {
@@ -333,43 +333,35 @@ export default function Page() {
flex: layout.review.state() === "pane",
}}
>
<div class="relative shrink-0 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full max-w-2xl mx-auto">
<div
classList={{
"relative shrink-0 py-3 flex flex-col gap-6 flex-1 min-h-0 w-full": true,
"max-w-146 mx-auto": !wide(),
}}
>
<Switch>
<Match when={session.id}>
<div class="flex items-start justify-start h-full min-h-0">
<Show when={session.messages.user().length > 1}>
<>
<MessageNav
class="@6xl:hidden mt-3 mr-8"
messages={session.messages.user()}
current={session.messages.active()}
onMessageSelect={session.messages.setActive}
size="compact"
working={session.working()}
/>
<MessageNav
classList={{
"hidden @6xl:flex": true,
"mt-0.5 mr-3 absolute right-full": wide(),
"mt-3 mr-8": !wide(),
}}
messages={session.messages.user()}
current={session.messages.active()}
onMessageSelect={session.messages.setActive}
size={wide() ? "normal" : "compact"}
working={session.working()}
/>
</>
</Show>
<SessionMessageRail
messages={session.messages.user()}
current={session.messages.active()}
onMessageSelect={session.messages.setActive}
working={session.working()}
wide={wide()}
/>
<SessionTurn
sessionID={session.id!}
messageID={session.messages.active()?.id!}
classes={{ root: "pb-20 flex-1 min-w-0", content: "pb-20", container: "px-6" }}
classes={{
root: "pb-20 flex-1 min-w-0",
content: "pb-20",
container: "w-full " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
}}
/>
</div>
</Match>
<Match when={true}>
<div class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch">
<div class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-146 mx-auto px-6">
<div class="text-20-medium text-text-weaker">New session</div>
<div class="flex justify-center items-center gap-3">
<Icon name="folder" size="small" />
@@ -390,12 +382,14 @@ export default function Page() {
</div>
</Match>
</Switch>
<div class="absolute inset-x-0 px-6 max-w-2xl flex flex-col justify-center items-center z-50 mx-auto bottom-8">
<PromptInput
ref={(el) => {
inputRef = el
}}
/>
<div class="absolute inset-x-0 bottom-8 flex flex-col justify-center items-center z-50">
<div class="w-full max-w-146 px-6">
<PromptInput
ref={(el) => {
inputRef = el
}}
/>
</div>
</div>
</div>
<Show when={layout.review.state() === "pane" && session.diffs().length}>
@@ -498,7 +492,7 @@ export default function Page() {
</DragOverlay>
</DragDropProvider>
<Show when={session.layout.tabs.active}>
<div class="absolute inset-x-0 px-6 max-w-2xl flex flex-col justify-center items-center z-50 mx-auto bottom-8">
<div class="absolute inset-x-0 px-6 max-w-146 flex flex-col justify-center items-center z-50 mx-auto bottom-8">
<PromptInput
ref={(el) => {
inputRef = el
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.0.118",
"version": "1.0.125",
"private": true,
"type": "module",
"scripts": {
+6 -1
View File
@@ -65,8 +65,11 @@ export namespace Share {
})
export async function data(id: string) {
let time = Date.now()
const list = await Storage.list(["share_data", id])
console.log("listing share data", Date.now() - time, list.length)
const promises = []
time = Date.now()
for (const item of list) {
promises.push(
iife(async () => {
@@ -78,7 +81,9 @@ export namespace Share {
}),
)
}
return await Promise.all(promises)
const result = await Promise.all(promises)
console.log("read share data", Date.now() - time, result.length)
return result
}
export const sync = fn(
+2
View File
@@ -9,6 +9,8 @@ export default createHandler(() => (
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OpenCode</title>
<meta name="theme-color" content="#F8F7F7" />
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
{assets}
@@ -12,7 +12,7 @@ import { iife } from "@opencode-ai/util/iife"
import { Binary } from "@opencode-ai/util/binary"
import { NamedError } from "@opencode-ai/util/error"
import { DateTime } from "luxon"
import { MessageNav } from "@opencode-ai/ui/message-nav"
import { SessionMessageRail } from "@opencode-ai/ui/session-message-rail"
import { createStore } from "solid-js/store"
import z from "zod"
import NotFound from "../[...404]"
@@ -41,6 +41,9 @@ const getData = query(async (shareID) => {
session_diff_preload: {
[sessionID: string]: PreloadMultiFileDiffResult<any>[]
}
session_diff_preload_split: {
[sessionID: string]: PreloadMultiFileDiffResult<any>[]
}
session_status: {
[sessionID: string]: SessionStatus
}
@@ -62,6 +65,9 @@ const getData = query(async (shareID) => {
session_diff_preload: {
[share.sessionID]: [],
},
session_diff_preload_split: {
[share.sessionID]: [],
},
session_status: {
[share.sessionID]: {
type: "idle",
@@ -78,16 +84,28 @@ const getData = query(async (shareID) => {
break
case "session_diff":
result.session_diff[share.sessionID] = item.data
result.session_diff_preload[share.sessionID] = await Promise.all(
item.data.map(async (diff) =>
preloadMultiFileDiff<any>({
oldFile: { name: diff.file, contents: diff.before },
newFile: { name: diff.file, contents: diff.after },
options: createDefaultOptions("unified"),
// annotations,
}),
),
)
await Promise.all([
Promise.all(
item.data.map(async (diff) =>
preloadMultiFileDiff<any>({
oldFile: { name: diff.file, contents: diff.before },
newFile: { name: diff.file, contents: diff.after },
options: createDefaultOptions("unified"),
// annotations,
}),
),
).then((r) => (result.session_diff_preload[share.sessionID] = r)),
Promise.all(
item.data.map(async (diff) =>
preloadMultiFileDiff<any>({
oldFile: { name: diff.file, contents: diff.before },
newFile: { name: diff.file, contents: diff.after },
options: createDefaultOptions("split"),
// annotations,
}),
),
).then((r) => (result.session_diff_preload_split[share.sessionID] = r)),
])
break
case "message":
result.message[item.data.sessionID] = result.message[item.data.sessionID] ?? []
@@ -169,9 +187,17 @@ export default function () {
preloaded: preloaded.find((d) => d.newFile.name === diff.file),
}))
})
const splitDiffs = createMemo(() => {
const diffs = data().session_diff[data().sessionID] ?? []
const preloaded = data().session_diff_preload_split[data().sessionID] ?? []
return diffs.map((diff) => ({
...diff,
preloaded: preloaded.find((d) => d.newFile.name === diff.file),
}))
})
const title = () => (
<div class="flex flex-col gap-4 shrink-0">
<div class="flex flex-col gap-4">
<div class="h-8 flex gap-4 items-center justify-start self-stretch">
<div class="pl-[2.5px] pr-2 flex items-center gap-1.75 bg-surface-strong shadow-xs-border-base">
<Mark class="shrink-0 w-3 my-0.5" />
@@ -215,7 +241,6 @@ export default function () {
)
const wide = createMemo(() => diffs().length === 0)
const columnPadding = () => (wide() ? "px-6" : "px-21 @4xl:px-6")
return (
<div class="relative bg-background-stronger w-screen h-screen overflow-hidden flex flex-col">
@@ -243,64 +268,48 @@ export default function () {
</div>
</header>
<div class="select-text flex flex-col flex-1 min-h-0">
<div class="hidden md:flex w-full flex-1 min-h-0">
<div classList={{ "hidden w-full flex-1 min-h-0": true, "md:flex": wide(), "lg:flex": !wide() }}>
<div
classList={{
"@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full mx-auto": true,
"max-w-2xl": true,
"@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full": true,
"mx-auto max-w-146": !wide(),
}}
>
<div class={columnPadding()}>{title()}</div>
<div
classList={{
"w-full flex justify-start items-start min-w-0": true,
"max-w-146 mx-auto px-6": wide(),
"pr-6 pl-18": !wide(),
}}
>
{title()}
</div>
<div class="flex items-start justify-start h-full min-h-0">
<Show when={messages().length > 1}>
<>
<div class="md:hidden absolute right-full">
<MessageNav
class="mt-2 mr-3"
messages={messages()}
current={activeMessage()}
onMessageSelect={setActiveMessage}
size="compact"
/>
</div>
<div
classList={{
"hidden md:block": true,
"absolute right-[90%]": !wide(),
"absolute right-full": wide(),
}}
>
<MessageNav
classList={{
"mt-2.5 mr-3": !wide(),
"mt-0.5 mr-8": wide(),
}}
messages={messages()}
current={activeMessage()}
onMessageSelect={setActiveMessage}
size={wide() ? "normal" : "compact"}
/>
</div>
</>
</Show>
<SessionMessageRail
messages={messages()}
current={activeMessage()}
onMessageSelect={setActiveMessage}
wide={wide()}
/>
<SessionTurn
sessionID={data().sessionID}
messageID={store.messageId ?? firstUserMessage()!.id!}
classes={{
root: "grow",
content: "flex flex-col justify-between",
container: `${columnPadding()} pb-20`,
content: "flex flex-col justify-between items-start",
container: "w-full pb-20 " + (wide() ? "max-w-146 mx-auto px-6" : "pr-6 pl-18"),
}}
>
<div class={`${columnPadding()} flex items-center justify-center pb-8 shrink-0`}>
<div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}>
<Logo class="w-58.5 opacity-12" />
</div>
</SessionTurn>
</div>
</div>
<Show when={diffs().length > 0}>
<div class="relative grow pt-14 flex-1 min-h-0 border-l border-border-weak-base">
<div class="@container relative grow pt-14 flex-1 min-h-0 border-l border-border-weak-base">
<SessionReview
class="@4xl:hidden"
diffs={diffs()}
classes={{
root: "pb-20",
@@ -308,12 +317,22 @@ export default function () {
container: "px-6",
}}
/>
<SessionReview
class="hidden @4xl:flex"
split
diffs={splitDiffs()}
classes={{
root: "pb-20",
header: "px-6",
container: "px-6",
}}
/>
</div>
</Show>
</div>
<Switch>
<Match when={diffs().length > 0}>
<Tabs class="md:hidden">
<Tabs classList={{ "md:hidden": wide(), "lg:hidden": !wide() }}>
<Tabs.List>
<Tabs.Trigger value="session" class="w-1/2" classes={{ button: "w-full" }}>
Session
@@ -344,7 +363,9 @@ export default function () {
</Tabs>
</Match>
<Match when={true}>
<div class="md:hidden !overflow-hidden">{turns()}</div>
<div classList={{ "!overflow-hidden": true, "md:hidden": wide(), "lg:hidden": !wide() }}>
{turns()}
</div>
</Match>
</Switch>
</div>
+6 -6
View File
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The AI coding agent built for the terminal"
version = "1.0.118"
version = "1.0.125"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/sst/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.118/opencode-darwin-arm64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.125/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.118/opencode-darwin-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.125/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.118/opencode-linux-arm64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.125/opencode-linux-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.118/opencode-linux-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.125/opencode-linux-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.118/opencode-windows-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.125/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.0.118",
"version": "1.0.125",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+10 -8
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.0.118",
"version": "1.0.125",
"name": "opencode",
"type": "module",
"private": true,
@@ -43,13 +43,15 @@
"@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.5.1",
"@ai-sdk/amazon-bedrock": "3.0.57",
"@ai-sdk/anthropic": "2.0.45",
"@ai-sdk/anthropic": "2.0.50",
"@ai-sdk/azure": "2.0.73",
"@ai-sdk/google": "2.0.42",
"@ai-sdk/google-vertex": "3.0.74",
"@ai-sdk/google": "2.0.44",
"@ai-sdk/google-vertex": "3.0.81",
"@ai-sdk/mcp": "0.0.8",
"@ai-sdk/openai": "2.0.71",
"@ai-sdk/openai-compatible": "1.0.27",
"@ai-sdk/provider": "2.0.0",
"@ai-sdk/provider-utils": "3.0.18",
"@clack/prompts": "1.0.0-alpha.1",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
@@ -61,9 +63,9 @@
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.2.5",
"@opentui/core": "0.1.50",
"@opentui/solid": "0.1.50",
"@openrouter/ai-sdk-provider": "1.2.8",
"@opentui/core": "0.1.54",
"@opentui/solid": "0.1.54",
"@parcel/watcher": "2.5.1",
"@pierre/precision-diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
@@ -82,7 +84,7 @@
"jsonc-parser": "3.3.1",
"minimatch": "10.0.3",
"open": "10.1.2",
"opentui-spinner": "0.0.5",
"opentui-spinner": "0.0.6",
"partial-json": "0.1.7",
"remeda": "catalog:",
"solid-js": "catalog:",
+36 -2
View File
@@ -102,8 +102,7 @@ export namespace Agent {
const result: Record<string, Info> = {
general: {
name: "general",
description:
"General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you.",
description: `General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel.`,
tools: {
todoread: false,
todowrite: false,
@@ -114,6 +113,41 @@ export namespace Agent {
mode: "subagent",
builtIn: true,
},
explore: {
name: "explore",
tools: {
todoread: false,
todowrite: false,
edit: false,
write: false,
...defaultTools,
},
description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.`,
prompt: [
`You are a file search specialist. You excel at thoroughly navigating and exploring codebases.`,
``,
`Your strengths:`,
`- Rapidly finding files using glob patterns`,
`- Searching code and text with powerful regex patterns`,
`- Reading and analyzing file contents`,
``,
`Guidelines:`,
`- Use Glob for broad file pattern matching`,
`- Use Grep for searching file contents with regex`,
`- Use Read when you know the specific file path you need to read`,
`- Use Bash for file operations like copying, moving, or listing directory contents`,
`- Adapt your search approach based on the thoroughness level specified by the caller`,
`- Return file paths as absolute paths in your final response`,
`- For clear communication, avoid using emojis`,
`- Do not create any files, or run bash commands that modify the user's system state in any way`,
``,
`Complete the user's search request efficiently and report your findings clearly.`,
].join("\n"),
options: {},
permission: agentPermission,
mode: "subagent",
builtIn: true,
},
build: {
name: "build",
tools: { ...defaultTools },
+31 -8
View File
@@ -7,19 +7,35 @@ import { GlobalBus } from "./global"
export namespace Bus {
const log = Log.create({ service: "bus" })
type Subscription = (event: any) => void
const state = Instance.state(() => {
const subscriptions = new Map<any, Subscription[]>()
return {
subscriptions,
}
})
const disposedEventType = "server.instance.disposed"
export type EventDefinition = ReturnType<typeof event>
const registry = new Map<string, EventDefinition>()
const state = Instance.state(
() => {
const subscriptions = new Map<any, Subscription[]>()
return {
subscriptions,
}
},
async (entry) => {
const wildcard = entry.subscriptions.get("*")
if (!wildcard) return
const event = {
type: disposedEventType,
properties: {
directory: Instance.directory,
},
}
for (const sub of [...wildcard]) {
sub(event)
}
},
)
export function event<Type extends string, Properties extends ZodType>(type: Type, properties: Properties) {
const result = {
type,
@@ -29,6 +45,13 @@ export namespace Bus {
return result
}
export const InstanceDisposed = event(
disposedEventType,
z.object({
directory: z.string(),
}),
)
export function payloads() {
return z
.discriminatedUnion(
+10
View File
@@ -30,6 +30,7 @@ import { TuiEvent } from "./event"
import { KVProvider, useKV } from "./context/kv"
import { Provider } from "@/provider/provider"
import { ArgsProvider, useArgs, type Args } from "./context/args"
import open from "open"
async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
// can't set raw mode if not a TTY
@@ -315,6 +316,15 @@ function App() {
},
category: "System",
},
{
title: "Open docs",
value: "docs.open",
onSelect: () => {
open("https://opencode.ai/docs").catch(() => {})
dialog.clear()
},
category: "System",
},
{
title: "Exit the app",
value: "app.exit",
@@ -6,6 +6,7 @@ import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
import { useDialog } from "@tui/ui/dialog"
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
import { Keybind } from "@/util/keybind"
import { iife } from "@/util/iife"
export function DialogModel() {
const local = useLocal()
@@ -21,73 +22,48 @@ export function DialogModel() {
const options = createMemo(() => {
const query = ref()?.filter
const favorites = local.model.favorite()
const favorites = connected() ? local.model.favorite() : []
const recents = local.model.recent()
const currentModel = local.model.current()
const orderedRecents = currentModel
? [
currentModel,
...recents.filter(
(item) => item.providerID !== currentModel.providerID || item.modelID !== currentModel.modelID,
),
]
: recents
const isCurrent = (item: { providerID: string; modelID: string }) =>
currentModel && item.providerID === currentModel.providerID && item.modelID === currentModel.modelID
const currentIsFavorite = currentModel && favorites.some((fav) => isCurrent(fav))
const recentList = orderedRecents
const recentList = recents
.filter((item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID))
.slice(0, 5)
const orderedFavorites = currentModel
? [...favorites.filter((item) => isCurrent(item)), ...favorites.filter((item) => !isCurrent(item))]
: favorites
const orderedRecentList =
currentModel && !currentIsFavorite
? [...recentList.filter((item) => isCurrent(item)), ...recentList.filter((item) => !isCurrent(item))]
: recentList
const favoriteOptions =
!query && favorites.length > 0
? orderedFavorites.flatMap((item) => {
const provider = sync.data.provider.find((x) => x.id === item.providerID)
if (!provider) return []
const model = provider.models[item.modelID]
if (!model) return []
return [
{
key: item,
value: {
providerID: provider.id,
modelID: model.id,
},
title: model.name ?? item.modelID,
description: `${provider.name}`,
category: "Favorites",
disabled: provider.id === "opencode" && model.id.includes("-nano"),
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
onSelect: () => {
dialog.clear()
local.model.set(
{
providerID: provider.id,
modelID: model.id,
},
{ recent: true },
)
},
const favoriteOptions = !query
? favorites.flatMap((item) => {
const provider = sync.data.provider.find((x) => x.id === item.providerID)
if (!provider) return []
const model = provider.models[item.modelID]
if (!model) return []
return [
{
key: item,
value: {
providerID: provider.id,
modelID: model.id,
},
]
})
: []
title: model.name ?? item.modelID,
description: provider.name,
category: "Favorites",
disabled: provider.id === "opencode" && model.id.includes("-nano"),
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
onSelect: () => {
dialog.clear()
local.model.set(
{
providerID: provider.id,
modelID: model.id,
},
{ recent: true },
)
},
},
]
})
: []
const recentOptions = !query
? orderedRecentList.flatMap((item) => {
? recentList.flatMap((item) => {
const provider = sync.data.provider.find((x) => x.id === item.providerID)
if (!provider) return []
const model = provider.models[item.modelID]
@@ -137,13 +113,14 @@ export function DialogModel() {
providerID: provider.id,
modelID: model,
}
const favorite = favorites.some(
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
)
return {
value,
title: info.name ?? model,
description: connected() ? `${provider.name}${favorite ? " ★" : ""}` : undefined,
description: favorites.some(
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
)
? "(Favorite)"
: undefined,
category: connected() ? provider.name : undefined,
disabled: provider.id === "opencode" && model.includes("-nano"),
footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
@@ -165,10 +142,10 @@ export function DialogModel() {
const inFavorites = favorites.some(
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
)
const inRecents = orderedRecents.some(
if (inFavorites) return false
const inRecents = recents.some(
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
)
if (inFavorites) return false
if (inRecents) return false
return true
}),
@@ -196,7 +173,7 @@ export function DialogModel() {
keybind={[
{
keybind: { ctrl: true, name: "a", meta: false, shift: false, leader: false },
title: connected() ? "Connect provider" : "More providers",
title: connected() ? "Connect provider" : "View all providers",
onTrigger() {
dialog.replace(() => <DialogProvider />)
},
@@ -204,6 +181,7 @@ export function DialogModel() {
{
keybind: Keybind.parse("ctrl+f")[0],
title: "Favorite",
disabled: !connected(),
onTrigger: (option) => {
local.model.toggleFavorite(option.value as { providerID: string; modelID: string })
},
@@ -26,13 +26,15 @@ export function createDialogProviderOptions() {
const options = createMemo(() => {
return pipe(
sync.data.provider_next.all,
sortBy((x) => PROVIDER_PRIORITY[x.id] ?? 99),
map((provider) => ({
title: provider.name,
value: provider.id,
footer: {
opencode: "Recommended",
anthropic: "Claude Max or API key",
description: {
opencode: "(Recommended)",
anthropic: "(Claude Max or API key)",
}[provider.id],
category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other",
async onSelect() {
const methods = sync.data.provider_auth[provider.id] ?? [
{
@@ -85,7 +87,6 @@ export function createDialogProviderOptions() {
}
},
})),
sortBy((x) => PROVIDER_PRIORITY[x.value] ?? 99),
)
})
return options
@@ -239,7 +239,7 @@ export function Autocomplete(props: {
},
{
display: "/thinking",
description: "toggle thinking blocks",
description: "toggle thinking visibility",
onSelect: () => command.trigger("session.toggle.thinking"),
},
)
@@ -586,12 +586,16 @@ export function Prompt(props: PromptProps) {
frames: createFrames({
color,
style: "blocks",
inactiveFactor: 0.25,
inactiveFactor: 0.6,
// enableFading: false,
minAlpha: 0.3,
}),
color: createColors({
color,
style: "blocks",
inactiveFactor: 0.25,
inactiveFactor: 0.6,
// enableFading: false,
minAlpha: 0.3,
}),
}
})
@@ -809,7 +813,8 @@ export function Prompt(props: PromptProps) {
borderColor={highlight()}
customBorderChars={{
...EmptyBorder,
vertical: "╹",
// when the background is transparent, don't draw the vertical line
vertical: theme.background.a != 0 ? "╹" : " ",
}}
>
<box
@@ -838,7 +843,8 @@ export function Prompt(props: PromptProps) {
justifyContent={status().type === "retry" ? "space-between" : "flex-start"}
>
<box flexShrink={0} flexDirection="row" gap={1}>
<spinner color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
{/* @ts-ignore // SpinnerOptions doesn't support marginLeft */}
<spinner marginLeft={1} color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
<box flexDirection="row" gap={1} flexShrink={0}>
{(() => {
const retry = createMemo(() => {
@@ -850,7 +856,7 @@ export function Prompt(props: PromptProps) {
const r = retry()
if (!r) return
if (r.message.includes("exceeded your current quota") && r.message.includes("gemini"))
return "gemini 3 way too hot right now"
return "gemini is way too hot right now"
if (r.message.length > 50) return r.message.slice(0, 50) + "..."
return r.message
})
@@ -81,6 +81,7 @@ const context = createContext<{
conceal: () => boolean
showThinking: () => boolean
showTimestamps: () => boolean
diffWrapMode: () => "word" | "none"
sync: ReturnType<typeof useSync>
}>()
@@ -111,8 +112,9 @@ export function Session() {
const dimensions = useTerminalDimensions()
const [sidebar, setSidebar] = createSignal<"show" | "hide" | "auto">(kv.get("sidebar", "auto"))
const [conceal, setConceal] = createSignal(true)
const [showThinking, setShowThinking] = createSignal(true)
const [showThinking, setShowThinking] = createSignal(kv.get("thinking_visibility", true))
const [showTimestamps, setShowTimestamps] = createSignal(kv.get("timestamps", "hide") === "show")
const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word")
const wide = createMemo(() => dimensions().width > 120)
const sidebarVisible = createMemo(() => {
@@ -430,11 +432,24 @@ export function Session() {
},
},
{
title: "Toggle thinking blocks",
title: showThinking() ? "Hide thinking" : "Show thinking",
value: "session.toggle.thinking",
category: "Session",
onSelect: (dialog) => {
setShowThinking((prev) => !prev)
setShowThinking((prev) => {
const next = !prev
kv.set("thinking_visibility", next)
return next
})
dialog.clear()
},
},
{
title: "Toggle diff wrapping",
value: "session.toggle.diffwrap",
category: "Session",
onSelect: (dialog) => {
setDiffWrapMode((prev) => (prev === "word" ? "none" : "word"))
dialog.clear()
},
},
@@ -739,6 +754,7 @@ export function Session() {
conceal,
showThinking,
showTimestamps,
diffWrapMode,
sync,
}}
>
@@ -1185,15 +1201,15 @@ function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMess
<box gap={1}>
<text fg={theme.text}>Permission required to run this tool:</text>
<box flexDirection="row" gap={2}>
<text>
<text fg={theme.text}>
<b>enter</b>
<span style={{ fg: theme.textMuted }}> accept</span>
</text>
<text>
<text fg={theme.text}>
<b>a</b>
<span style={{ fg: theme.textMuted }}> accept always</span>
</text>
<text>
<text fg={theme.text}>
<b>d</b>
<span style={{ fg: theme.textMuted }}> deny</span>
</text>
@@ -1298,21 +1314,9 @@ ToolRegistry.register<typeof WriteTool>({
container: "block",
render(props) {
const { theme, syntax } = useTheme()
const lines = createMemo(
() => (typeof props.input.content === "string" ? props.input.content.split("\n") : []),
[] as string[],
)
const code = createMemo(() => {
if (!props.input.content) return ""
const text = props.input.content
return text
})
const numbers = createMemo(() => {
const pad = lines().length.toString().length
return lines()
.map((_, index) => index + 1)
.map((x) => x.toString().padStart(pad, " "))
return props.input.content
})
const diagnostics = createMemo(() => props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? [])
@@ -1322,14 +1326,15 @@ ToolRegistry.register<typeof WriteTool>({
<ToolTitle icon="←" fallback="Preparing write..." when={props.input.filePath}>
Wrote {props.input.filePath}
</ToolTitle>
<box flexDirection="row">
<box flexShrink={0}>
<For each={numbers()}>{(value) => <text style={{ fg: theme.textMuted }}>{value}</text>}</For>
</box>
<box paddingLeft={1} flexGrow={1}>
<code fg={theme.text} filetype={filetype(props.input.filePath!)} syntaxStyle={syntax()} content={code()} />
</box>
</box>
<line_number fg={theme.textMuted} minWidth={3} paddingRight={1}>
<code
conceal={false}
fg={theme.text}
filetype={filetype(props.input.filePath!)}
syntaxStyle={syntax()}
content={code()}
/>
</line_number>
<Show when={diagnostics().length}>
<For each={diagnostics()}>
{(diagnostic) => (
@@ -1401,15 +1406,15 @@ ToolRegistry.register<typeof TaskTool>({
return (
<>
<ToolTitle icon="%" fallback="Delegating..." when={props.input.subagent_type ?? props.input.description}>
Task [{props.input.subagent_type ?? "unknown"}] {props.input.description}
<ToolTitle icon="" fallback="Delegating..." when={props.input.subagent_type ?? props.input.description}>
{Locale.titlecase(props.input.subagent_type ?? "unknown")} Task "{props.input.description}"
</ToolTitle>
<Show when={props.metadata.summary?.length}>
<box>
<For each={props.metadata.summary ?? []}>
{(task) => (
<text style={{ fg: theme.textMuted }}>
{task.tool} {task.state.status === "completed" ? task.state.title : ""}
{Locale.titlecase(task.tool)} {task.state.status === "completed" ? task.state.title : ""}
</text>
)}
</For>
@@ -1471,84 +1476,17 @@ ToolRegistry.register<typeof EditTool>({
const ctx = use()
const { theme, syntax } = useTheme()
const style = createMemo(() => {
const view = createMemo(() => {
const diffStyle = ctx.sync.data.config.tui?.diff_style
if (diffStyle === "stacked") return "stacked"
if (diffStyle === "stacked") return "unified"
// Default to "auto" behavior
return ctx.width > 120 ? "split" : "stacked"
})
const diff = createMemo(() => {
const diff = props.metadata.diff ?? props.permission["diff"]
if (!diff) return null
try {
const patches = parsePatch(diff)
if (patches.length === 0) return null
const patch = patches[0]
const oldLines: string[] = []
const newLines: string[] = []
for (const hunk of patch.hunks) {
let i = 0
while (i < hunk.lines.length) {
const line = hunk.lines[i]
if (line.startsWith("-")) {
const removedLines: string[] = []
while (i < hunk.lines.length && hunk.lines[i].startsWith("-")) {
removedLines.push("- " + hunk.lines[i].slice(1))
i++
}
const addedLines: string[] = []
while (i < hunk.lines.length && hunk.lines[i].startsWith("+")) {
addedLines.push("+ " + hunk.lines[i].slice(1))
i++
}
const maxLen = Math.max(removedLines.length, addedLines.length)
for (let j = 0; j < maxLen; j++) {
oldLines.push(removedLines[j] ?? "")
newLines.push(addedLines[j] ?? "")
}
} else if (line.startsWith("+")) {
const addedLines: string[] = []
while (i < hunk.lines.length && hunk.lines[i].startsWith("+")) {
addedLines.push("+ " + hunk.lines[i].slice(1))
i++
}
for (const added of addedLines) {
oldLines.push("")
newLines.push(added)
}
} else {
oldLines.push(" " + line.slice(1))
newLines.push(" " + line.slice(1))
i++
}
}
}
return {
oldContent: oldLines.join("\n"),
newContent: newLines.join("\n"),
}
} catch (error) {
return null
}
})
const code = createMemo(() => {
if (!props.metadata.diff) return ""
const text = props.metadata.diff.split("\n").slice(5).join("\n")
return text.trim()
return ctx.width > 120 ? "split" : "unified"
})
const ft = createMemo(() => filetype(props.input.filePath))
const diffContent = createMemo(() => props.metadata.diff ?? props.permission["diff"])
const diagnostics = createMemo(() => {
const arr = props.metadata.diagnostics?.[props.input.filePath ?? ""] ?? []
return arr.filter((x) => x.severity === 1).slice(0, 3)
@@ -1562,26 +1500,28 @@ ToolRegistry.register<typeof EditTool>({
replaceAll: props.input.replaceAll,
})}
</ToolTitle>
<Switch>
<Match when={props.permission["diff"]}>
<text fg={theme.text}>{props.permission["diff"]?.trim()}</text>
</Match>
<Match when={diff() && style() === "split"}>
<box paddingLeft={1} flexDirection="row" gap={2}>
<box flexGrow={1} flexBasis={0}>
<code fg={theme.text} filetype={ft()} syntaxStyle={syntax()} content={diff()!.oldContent} />
</box>
<box flexGrow={1} flexBasis={0}>
<code fg={theme.text} filetype={ft()} syntaxStyle={syntax()} content={diff()!.newContent} />
</box>
</box>
</Match>
<Match when={code()}>
<box paddingLeft={1}>
<code fg={theme.text} filetype={ft()} syntaxStyle={syntax()} content={code()} />
</box>
</Match>
</Switch>
<Show when={diffContent()}>
<box paddingLeft={1}>
<diff
diff={diffContent()}
view={view()}
filetype={ft()}
syntaxStyle={syntax()}
showLineNumbers={true}
width="100%"
wrapMode={ctx.diffWrapMode()}
addedBg={theme.diffAddedBg}
removedBg={theme.diffRemovedBg}
contextBg={theme.diffContextBg}
addedSignColor={theme.diffHighlightAdded}
removedSignColor={theme.diffHighlightRemoved}
lineNumberFg={theme.diffLineNumber}
lineNumberBg={theme.diffContextBg}
addedLineNumberBg={theme.diffAddedLineNumberBg}
removedLineNumberBg={theme.diffRemovedLineNumberBg}
/>
</box>
</Show>
<Show when={diagnostics().length}>
<box>
<For each={diagnostics()}>
@@ -55,9 +55,6 @@ export function DialogPrompt(props: DialogPromptProps) {
<text fg={theme.text}>
enter <span style={{ fg: theme.textMuted }}>submit</span>
</text>
<text fg={theme.text}>
esc <span style={{ fg: theme.textMuted }}>cancel</span>
</text>
</box>
</box>
)
@@ -13,6 +13,7 @@ import { Locale } from "@/util/locale"
export interface DialogSelectProps<T> {
title: string
placeholder?: string
options: DialogSelectOption<T>[]
ref?: (ref: DialogSelectRef<T>) => void
onMove?: (option: DialogSelectOption<T>) => void
@@ -21,6 +22,7 @@ export interface DialogSelectProps<T> {
keybind?: {
keybind: Keybind.Info
title: string
disabled?: boolean
onTrigger: (option: DialogSelectOption<T>) => void
}[]
current?: T
@@ -150,6 +152,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
}
for (const item of props.keybind ?? []) {
if (item.disabled) continue
if (Keybind.match(item.keybind, keybind.parse(evt))) {
const s = selected()
if (s) {
@@ -171,8 +174,10 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
}
props.ref?.(ref)
const keybinds = createMemo(() => props.keybind?.filter((x) => !x.disabled) ?? [])
return (
<box gap={1}>
<box gap={1} paddingBottom={1}>
<box paddingLeft={4} paddingRight={4}>
<box flexDirection="row" justifyContent="space-between">
<text fg={theme.text} attributes={TextAttributes.BOLD}>
@@ -195,7 +200,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
input = r
setTimeout(() => input.focus(), 1)
}}
placeholder="Enter search term"
placeholder={props.placeholder ?? "Search"}
/>
</box>
</box>
@@ -253,18 +258,20 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
)}
</For>
</scrollbox>
<box paddingRight={2} paddingLeft={4} flexDirection="row" paddingBottom={1} gap={2}>
<For each={props.keybind ?? []}>
{(item) => (
<text>
<span style={{ fg: theme.text }}>
<b>{item.title}</b>{" "}
</span>
<span style={{ fg: theme.textMuted }}>{Keybind.toString(item.keybind)}</span>
</text>
)}
</For>
</box>
<Show when={keybinds().length} fallback={<box flexShrink={0} />}>
<box paddingRight={2} paddingLeft={4} flexDirection="row" gap={2} flexShrink={0} paddingTop={1}>
<For each={keybinds()}>
{(item) => (
<text>
<span style={{ fg: theme.text }}>
<b>{item.title}</b>{" "}
</span>
<span style={{ fg: theme.textMuted }}>{Keybind.toString(item.keybind)}</span>
</text>
)}
</For>
</box>
</Show>
</box>
)
}
+52 -39
View File
@@ -8,6 +8,8 @@ interface AdvancedGradientOptions {
defaultColor?: ColorInput
direction?: "forward" | "backward" | "bidirectional"
holdFrames?: { start?: number; end?: number }
enableFading?: boolean
minAlpha?: number
}
interface ScannerState {
@@ -137,13 +139,16 @@ function calculateColorIndex(
}
function createKnightRiderTrail(options: AdvancedGradientOptions): ColorGenerator {
const { colors, defaultColor } = options
const { colors, defaultColor, enableFading = true, minAlpha = 0 } = options
// Use the provided defaultColor if it's an RGBA instance, otherwise convert/default
// We use RGBA.fromHex for the fallback to ensure we have an RGBA object.
// Note: If defaultColor is a string, we convert it once here.
const defaultRgba = defaultColor instanceof RGBA ? defaultColor : RGBA.fromHex((defaultColor as string) || "#000000")
// Store the base alpha from the inactive factor
const baseInactiveAlpha = defaultRgba.a
let cachedFrameIndex = -1
let cachedState: ScannerState | null = null
@@ -160,22 +165,22 @@ function createKnightRiderTrail(options: AdvancedGradientOptions): ColorGenerato
// Calculate global fade for inactive dots during hold or movement
const { isHolding, holdProgress, holdTotal, movementProgress, movementTotal } = state
let alpha = 1.0
if (isHolding && holdTotal > 0) {
// Fade out linearly
const progress = Math.min(holdProgress / holdTotal, 1)
alpha = Math.max(0, 1 - progress)
} else if (!isHolding && movementTotal > 0) {
// Fade in linearly during movement
const progress = Math.min(movementProgress / Math.max(1, movementTotal - 1), 1)
alpha = progress
let fadeFactor = 1.0
if (enableFading) {
if (isHolding && holdTotal > 0) {
// Fade out linearly to minAlpha
const progress = Math.min(holdProgress / holdTotal, 1)
fadeFactor = Math.max(minAlpha, 1 - progress * (1 - minAlpha))
} else if (!isHolding && movementTotal > 0) {
// Fade in linearly from minAlpha during movement
const progress = Math.min(movementProgress / Math.max(1, movementTotal - 1), 1)
fadeFactor = minAlpha + progress * (1 - minAlpha)
}
}
// Mutate the alpha of the default RGBA object
// This assumes single-threaded, synchronous rendering per frame
// where we can modify the state for the current frame.
// Since this is run for every char in the frame, setting it repeatedly to the same value is fine.
defaultRgba.a = alpha
// Combine base inactive alpha with the fade factor
// This ensures inactiveFactor is respected while still allowing fading animation
defaultRgba.a = baseInactiveAlpha * fadeFactor
if (index === -1) {
return defaultRgba
@@ -186,10 +191,10 @@ function createKnightRiderTrail(options: AdvancedGradientOptions): ColorGenerato
}
/**
* Derives a gradient of tail colors from a single bright color
* Derives a gradient of tail colors from a single bright color using alpha falloff
* @param brightColor The brightest color (center/head of the scanner)
* @param steps Number of gradient steps (default: 6)
* @returns Array of RGBA colors from brightest to darkest
* @returns Array of RGBA colors with alpha-based trail fade (background-independent)
*/
export function deriveTrailColors(brightColor: ColorInput, steps: number = 6): RGBA[] {
const baseRgba = brightColor instanceof RGBA ? brightColor : RGBA.fromHex(brightColor as string)
@@ -197,45 +202,45 @@ export function deriveTrailColors(brightColor: ColorInput, steps: number = 6): R
const colors: RGBA[] = []
for (let i = 0; i < steps; i++) {
// Progressive darkening:
// i=0: 100% brightness (original color)
// i=1: add slight bloom/glare (lighten)
// i=2+: progressively darken
let factor: number
// Alpha-based falloff with optional bloom effect
let alpha: number
let brightnessFactor: number
if (i === 0) {
factor = 1.0 // Original brightness
// Lead position: full brightness and opacity
alpha = 1.0
brightnessFactor = 1.0
} else if (i === 1) {
factor = 1.2 // Slight bloom/glare effect
// Slight bloom/glare effect: brighten color but reduce opacity slightly
alpha = 0.9
brightnessFactor = 1.15
} else {
// Exponential decay for natural-looking trail fade
factor = Math.pow(0.6, i - 1)
// Exponential alpha decay for natural-looking trail fade
alpha = Math.pow(0.65, i - 1)
brightnessFactor = 1.0
}
const r = Math.min(1.0, baseRgba.r * factor)
const g = Math.min(1.0, baseRgba.g * factor)
const b = Math.min(1.0, baseRgba.b * factor)
const r = Math.min(1.0, baseRgba.r * brightnessFactor)
const g = Math.min(1.0, baseRgba.g * brightnessFactor)
const b = Math.min(1.0, baseRgba.b * brightnessFactor)
colors.push(RGBA.fromValues(r, g, b, 1.0))
colors.push(RGBA.fromValues(r, g, b, alpha))
}
return colors
}
/**
* Derives the inactive/default color from a bright color
* Derives the inactive/default color from a bright color using alpha
* @param brightColor The brightest color (center/head of the scanner)
* @param factor Brightness factor for inactive color (default: 0.2)
* @returns A much darker version suitable for inactive dots
* @param factor Alpha factor for inactive color (default: 0.2, range: 0-1)
* @returns The same color with reduced alpha for background-independent dimming
*/
export function deriveInactiveColor(brightColor: ColorInput, factor: number = 0.2): RGBA {
const baseRgba = brightColor instanceof RGBA ? brightColor : RGBA.fromHex(brightColor as string)
const r = baseRgba.r * factor
const g = baseRgba.g * factor
const b = baseRgba.b * factor
return RGBA.fromValues(r, g, b, 1.0)
// Use the full color brightness but adjust alpha for background-independent dimming
return RGBA.fromValues(baseRgba.r, baseRgba.g, baseRgba.b, factor)
}
export type KnightRiderStyle = "blocks" | "diamonds"
@@ -251,8 +256,12 @@ export interface KnightRiderOptions {
/** Number of trail steps when using single color (default: 6) */
trailSteps?: number
defaultColor?: ColorInput
/** Brightness factor for inactive color when using single color (default: 0.2) */
/** Alpha factor for inactive color when using single color (default: 0.2, range: 0-1) */
inactiveFactor?: number
/** Enable fading of inactive dots during hold and movement (default: true) */
enableFading?: boolean
/** Minimum alpha value when fading (default: 0, range: 0-1) */
minAlpha?: number
}
/**
@@ -289,6 +298,8 @@ export function createFrames(options: KnightRiderOptions = {}): string[] {
defaultColor,
direction: "bidirectional" as const,
holdFrames: { start: holdStart, end: holdEnd },
enableFading: options.enableFading,
minAlpha: options.minAlpha,
}
// Bidirectional cycle: Forward (width) + Hold End + Backward (width-1) + Hold Start
@@ -349,6 +360,8 @@ export function createColors(options: KnightRiderOptions = {}): ColorGenerator {
defaultColor,
direction: "bidirectional" as const,
holdFrames: { start: holdStart, end: holdEnd },
enableFading: options.enableFading,
minAlpha: options.minAlpha,
}
return createKnightRiderTrail(trailOptions)
+1
View File
@@ -527,6 +527,7 @@ export namespace Config {
plan: Agent.optional(),
build: Agent.optional(),
general: Agent.optional(),
explore: Agent.optional(),
})
.catchall(Agent)
.optional()
-2
View File
@@ -47,7 +47,6 @@ export namespace FileWatcher {
const subscribe: ParcelWatcher.SubscribeCallback = (err, evts) => {
if (err) return
for (const evt of evts) {
log.info("event", evt)
if (evt.type === "create") Bus.publish(Event.Updated, { file: evt.path, event: "add" })
if (evt.type === "update") Bus.publish(Event.Updated, { file: evt.path, event: "change" })
if (evt.type === "delete") Bus.publish(Event.Updated, { file: evt.path, event: "unlink" })
@@ -83,7 +82,6 @@ export namespace FileWatcher {
)
export function init() {
// if (!Flag.OPENCODE_EXPERIMENTAL_WATCHER) return
state()
}
}
@@ -246,3 +246,12 @@ export const htmlbeautifier: Info = {
return Bun.which("htmlbeautifier") !== null
},
}
export const dart: Info = {
name: "dart",
command: ["dart", "format", "$FILE"],
extensions: [".dart"],
async enabled() {
return Bun.which("dart") !== null
},
}
+1 -1
View File
@@ -30,7 +30,7 @@ await Promise.all([
fs.mkdir(Global.Path.bin, { recursive: true }),
])
const CACHE_VERSION = "11"
const CACHE_VERSION = "12"
const version = await Bun.file(path.join(Global.Path.cache, "version"))
.text()
+18
View File
@@ -1166,4 +1166,22 @@ export namespace LSPServer {
}
},
}
export const Dart: Info = {
id: "dart",
extensions: [".dart"],
root: NearestRoot(["pubspec.yaml", "analysis_options.yaml"]),
async spawn(root) {
const dart = Bun.which("dart")
if (!dart) {
log.info("dart not found, please install dart first")
return
}
return {
process: spawn(dart, ["language-server", "--lsp"], {
cwd: root,
}),
}
},
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ export namespace Plugin {
const plugins = [...(config.plugin ?? [])]
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
plugins.push("opencode-copilot-auth@0.0.7")
plugins.push("opencode-anthropic-auth@0.0.2")
plugins.push("opencode-anthropic-auth@0.0.3")
}
for (let plugin of plugins) {
log.info("loading plugin", { path: plugin })
+4 -4
View File
@@ -75,18 +75,18 @@ export namespace Project {
await Storage.write<Info>(["project", "global"], project)
return project
}
worktree = await $`git rev-parse --path-format=absolute --show-toplevel`
worktree = await $`git rev-parse --show-toplevel`
.quiet()
.nothrow()
.cwd(worktree)
.text()
.then((x) => x.trim())
const vcsDir = await $`git rev-parse --path-format=absolute --git-dir`
.then((x) => path.resolve(worktree, x.trim()))
const vcsDir = await $`git rev-parse --git-dir`
.quiet()
.nothrow()
.cwd(worktree)
.text()
.then((x) => x.trim())
.then((x) => path.resolve(worktree, x.trim()))
const project: Info = {
id,
worktree,
+54 -9
View File
@@ -23,6 +23,7 @@ import { createVertexAnthropic } from "@ai-sdk/google-vertex/anthropic"
import { createOpenAI } from "@ai-sdk/openai"
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
import { createOpenRouter } from "@openrouter/ai-sdk-provider"
import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from "./sdk/openai-compatible/src"
export namespace Provider {
const log = Log.create({ service: "provider" })
@@ -37,6 +38,8 @@ export namespace Provider {
"@ai-sdk/openai": createOpenAI,
"@ai-sdk/openai-compatible": createOpenAICompatible,
"@openrouter/ai-sdk-provider": createOpenRouter,
// @ts-ignore (TODO: kill this code so we dont have to maintain it)
"@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible,
}
type CustomLoader = (provider: ModelsDev.Provider) => Promise<{
@@ -87,6 +90,30 @@ export namespace Provider {
options: {},
}
},
"github-copilot": async () => {
return {
autoload: false,
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
if (modelID.includes("gpt-5")) {
return sdk.responses(modelID)
}
return sdk.chat(modelID)
},
options: {},
}
},
"github-copilot-enterprise": async () => {
return {
autoload: false,
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
if (modelID.includes("gpt-5")) {
return sdk.responses(modelID)
}
return sdk.chat(modelID)
},
options: {},
}
},
azure: async () => {
return {
autoload: false,
@@ -428,15 +455,6 @@ export namespace Provider {
}
}
// load custom
for (const [providerID, fn] of Object.entries(CUSTOM_LOADERS)) {
if (disabled.has(providerID)) continue
const result = await fn(database[providerID])
if (result && (result.autoload || providers[providerID])) {
mergeProvider(providerID, result.options ?? {}, "custom", result.getModel)
}
}
for (const plugin of await Plugin.list()) {
if (!plugin.auth) continue
const providerID = plugin.auth.provider
@@ -478,6 +496,14 @@ export namespace Provider {
}
}
for (const [providerID, fn] of Object.entries(CUSTOM_LOADERS)) {
if (disabled.has(providerID)) continue
const result = await fn(database[providerID])
if (result && (result.autoload || providers[providerID])) {
mergeProvider(providerID, result.options ?? {}, "custom", result.getModel)
}
}
// load config
for (const [providerID, provider] of configProviders) {
mergeProvider(providerID, provider.options ?? {}, "config")
@@ -489,6 +515,10 @@ export namespace Provider {
continue
}
if (providerID === "github-copilot") {
provider.info.npm = "@ai-sdk/github-copilot"
}
const configProvider = config.provider?.[providerID]
const filteredModels = Object.fromEntries(
Object.entries(provider.info.models)
@@ -677,6 +707,21 @@ export namespace Provider {
}
}
export async function closest(providerID: string, query: string[]) {
const s = await state()
const provider = s.providers[providerID]
if (!provider) return undefined
for (const item of query) {
for (const modelID of Object.keys(provider.info.models)) {
if (modelID.includes(item))
return {
providerID,
modelID,
}
}
}
}
export async function getSmallModel(providerID: string) {
const cfg = await Config.get()
@@ -0,0 +1,5 @@
This is a temporary package used primarily for github copilot compatibility.
Avoid making changes to these files unless you want to only affect Copilot provider.
Also this should ONLY be used for Copilot provider.
@@ -0,0 +1,2 @@
export { createOpenaiCompatible, openaiCompatible } from "./openai-compatible-provider"
export type { OpenaiCompatibleProvider, OpenaiCompatibleProviderSettings } from "./openai-compatible-provider"
@@ -0,0 +1,100 @@
import type { LanguageModelV2 } from "@ai-sdk/provider"
import { OpenAICompatibleChatLanguageModel } from "@ai-sdk/openai-compatible"
import { type FetchFunction, withoutTrailingSlash, withUserAgentSuffix } from "@ai-sdk/provider-utils"
import { OpenAIResponsesLanguageModel } from "./responses/openai-responses-language-model"
// Import the version or define it
const VERSION = "0.1.0"
export type OpenaiCompatibleModelId = string
export interface OpenaiCompatibleProviderSettings {
/**
* API key for authenticating requests.
*/
apiKey?: string
/**
* Base URL for the OpenAI Compatible API calls.
*/
baseURL?: string
/**
* Name of the provider.
*/
name?: string
/**
* Custom headers to include in the requests.
*/
headers?: Record<string, string>
/**
* Custom fetch implementation.
*/
fetch?: FetchFunction
}
export interface OpenaiCompatibleProvider {
(modelId: OpenaiCompatibleModelId): LanguageModelV2
chat(modelId: OpenaiCompatibleModelId): LanguageModelV2
responses(modelId: OpenaiCompatibleModelId): LanguageModelV2
languageModel(modelId: OpenaiCompatibleModelId): LanguageModelV2
// embeddingModel(modelId: any): EmbeddingModelV2
// imageModel(modelId: any): ImageModelV2
}
/**
* Create an OpenAI Compatible provider instance.
*/
export function createOpenaiCompatible(options: OpenaiCompatibleProviderSettings = {}): OpenaiCompatibleProvider {
const baseURL = withoutTrailingSlash(options.baseURL ?? "https://api.openai.com/v1")
if (!baseURL) {
throw new Error("baseURL is required")
}
// Merge headers: defaults first, then user overrides
const headers = {
// Default OpenAI Compatible headers (can be overridden by user)
...(options.apiKey && { Authorization: `Bearer ${options.apiKey}` }),
...options.headers,
}
const getHeaders = () => withUserAgentSuffix(headers, `ai-sdk/openai-compatible/${VERSION}`)
const createChatModel = (modelId: OpenaiCompatibleModelId) => {
return new OpenAICompatibleChatLanguageModel(modelId, {
provider: `${options.name ?? "openai-compatible"}.chat`,
headers: getHeaders,
url: ({ path }) => `${baseURL}${path}`,
fetch: options.fetch,
})
}
const createResponsesModel = (modelId: OpenaiCompatibleModelId) => {
return new OpenAIResponsesLanguageModel(modelId, {
provider: `${options.name ?? "openai-compatible"}.responses`,
headers: getHeaders,
url: ({ path }) => `${baseURL}${path}`,
fetch: options.fetch,
})
}
const createLanguageModel = (modelId: OpenaiCompatibleModelId) => createChatModel(modelId)
const provider = function (modelId: OpenaiCompatibleModelId) {
return createChatModel(modelId)
}
provider.languageModel = createLanguageModel
provider.chat = createChatModel
provider.responses = createResponsesModel
return provider as OpenaiCompatibleProvider
}
// Default OpenAI Compatible provider instance
export const openaiCompatible = createOpenaiCompatible()
@@ -0,0 +1,303 @@
import {
type LanguageModelV2CallWarning,
type LanguageModelV2Prompt,
type LanguageModelV2ToolCallPart,
UnsupportedFunctionalityError,
} from "@ai-sdk/provider"
import { convertToBase64, parseProviderOptions } from "@ai-sdk/provider-utils"
import { z } from "zod/v4"
import type { OpenAIResponsesInput, OpenAIResponsesReasoning } from "./openai-responses-api-types"
import { localShellInputSchema, localShellOutputSchema } from "./tool/local-shell"
/**
* Check if a string is a file ID based on the given prefixes
* Returns false if prefixes is undefined (disables file ID detection)
*/
function isFileId(data: string, prefixes?: readonly string[]): boolean {
if (!prefixes) return false
return prefixes.some((prefix) => data.startsWith(prefix))
}
export async function convertToOpenAIResponsesInput({
prompt,
systemMessageMode,
fileIdPrefixes,
store,
hasLocalShellTool = false,
}: {
prompt: LanguageModelV2Prompt
systemMessageMode: "system" | "developer" | "remove"
fileIdPrefixes?: readonly string[]
store: boolean
hasLocalShellTool?: boolean
}): Promise<{
input: OpenAIResponsesInput
warnings: Array<LanguageModelV2CallWarning>
}> {
const input: OpenAIResponsesInput = []
const warnings: Array<LanguageModelV2CallWarning> = []
for (const { role, content } of prompt) {
switch (role) {
case "system": {
switch (systemMessageMode) {
case "system": {
input.push({ role: "system", content })
break
}
case "developer": {
input.push({ role: "developer", content })
break
}
case "remove": {
warnings.push({
type: "other",
message: "system messages are removed for this model",
})
break
}
default: {
const _exhaustiveCheck: never = systemMessageMode
throw new Error(`Unsupported system message mode: ${_exhaustiveCheck}`)
}
}
break
}
case "user": {
input.push({
role: "user",
content: content.map((part, index) => {
switch (part.type) {
case "text": {
return { type: "input_text", text: part.text }
}
case "file": {
if (part.mediaType.startsWith("image/")) {
const mediaType = part.mediaType === "image/*" ? "image/jpeg" : part.mediaType
return {
type: "input_image",
...(part.data instanceof URL
? { image_url: part.data.toString() }
: typeof part.data === "string" && isFileId(part.data, fileIdPrefixes)
? { file_id: part.data }
: {
image_url: `data:${mediaType};base64,${convertToBase64(part.data)}`,
}),
detail: part.providerOptions?.openai?.imageDetail,
}
} else if (part.mediaType === "application/pdf") {
if (part.data instanceof URL) {
return {
type: "input_file",
file_url: part.data.toString(),
}
}
return {
type: "input_file",
...(typeof part.data === "string" && isFileId(part.data, fileIdPrefixes)
? { file_id: part.data }
: {
filename: part.filename ?? `part-${index}.pdf`,
file_data: `data:application/pdf;base64,${convertToBase64(part.data)}`,
}),
}
} else {
throw new UnsupportedFunctionalityError({
functionality: `file part media type ${part.mediaType}`,
})
}
}
}
}),
})
break
}
case "assistant": {
const reasoningMessages: Record<string, OpenAIResponsesReasoning> = {}
const toolCallParts: Record<string, LanguageModelV2ToolCallPart> = {}
for (const part of content) {
switch (part.type) {
case "text": {
input.push({
role: "assistant",
content: [{ type: "output_text", text: part.text }],
id: (part.providerOptions?.openai?.itemId as string) ?? undefined,
})
break
}
case "tool-call": {
toolCallParts[part.toolCallId] = part
if (part.providerExecuted) {
break
}
if (hasLocalShellTool && part.toolName === "local_shell") {
const parsedInput = localShellInputSchema.parse(part.input)
input.push({
type: "local_shell_call",
call_id: part.toolCallId,
id: (part.providerOptions?.openai?.itemId as string) ?? undefined,
action: {
type: "exec",
command: parsedInput.action.command,
timeout_ms: parsedInput.action.timeoutMs,
user: parsedInput.action.user,
working_directory: parsedInput.action.workingDirectory,
env: parsedInput.action.env,
},
})
break
}
input.push({
type: "function_call",
call_id: part.toolCallId,
name: part.toolName,
arguments: JSON.stringify(part.input),
id: (part.providerOptions?.openai?.itemId as string) ?? undefined,
})
break
}
// assistant tool result parts are from provider-executed tools:
case "tool-result": {
if (store) {
// use item references to refer to tool results from built-in tools
input.push({ type: "item_reference", id: part.toolCallId })
} else {
warnings.push({
type: "other",
message: `Results for OpenAI tool ${part.toolName} are not sent to the API when store is false`,
})
}
break
}
case "reasoning": {
const providerOptions = await parseProviderOptions({
provider: "openai",
providerOptions: part.providerOptions,
schema: openaiResponsesReasoningProviderOptionsSchema,
})
const reasoningId = providerOptions?.itemId
if (reasoningId != null) {
const reasoningMessage = reasoningMessages[reasoningId]
if (store) {
if (reasoningMessage === undefined) {
// use item references to refer to reasoning (single reference)
input.push({ type: "item_reference", id: reasoningId })
// store unused reasoning message to mark id as used
reasoningMessages[reasoningId] = {
type: "reasoning",
id: reasoningId,
summary: [],
}
}
} else {
const summaryParts: Array<{
type: "summary_text"
text: string
}> = []
if (part.text.length > 0) {
summaryParts.push({
type: "summary_text",
text: part.text,
})
} else if (reasoningMessage !== undefined) {
warnings.push({
type: "other",
message: `Cannot append empty reasoning part to existing reasoning sequence. Skipping reasoning part: ${JSON.stringify(part)}.`,
})
}
if (reasoningMessage === undefined) {
reasoningMessages[reasoningId] = {
type: "reasoning",
id: reasoningId,
encrypted_content: providerOptions?.reasoningEncryptedContent,
summary: summaryParts,
}
input.push(reasoningMessages[reasoningId])
} else {
reasoningMessage.summary.push(...summaryParts)
}
}
} else {
warnings.push({
type: "other",
message: `Non-OpenAI reasoning parts are not supported. Skipping reasoning part: ${JSON.stringify(part)}.`,
})
}
break
}
}
}
break
}
case "tool": {
for (const part of content) {
const output = part.output
if (hasLocalShellTool && part.toolName === "local_shell" && output.type === "json") {
input.push({
type: "local_shell_call_output",
call_id: part.toolCallId,
output: localShellOutputSchema.parse(output.value).output,
})
break
}
let contentValue: string
switch (output.type) {
case "text":
case "error-text":
contentValue = output.value
break
case "content":
case "json":
case "error-json":
contentValue = JSON.stringify(output.value)
break
}
input.push({
type: "function_call_output",
call_id: part.toolCallId,
output: contentValue,
})
}
break
}
default: {
const _exhaustiveCheck: never = role
throw new Error(`Unsupported role: ${_exhaustiveCheck}`)
}
}
}
return { input, warnings }
}
const openaiResponsesReasoningProviderOptionsSchema = z.object({
itemId: z.string().nullish(),
reasoningEncryptedContent: z.string().nullish(),
})
export type OpenAIResponsesReasoningProviderOptions = z.infer<typeof openaiResponsesReasoningProviderOptionsSchema>
@@ -0,0 +1,22 @@
import type { LanguageModelV2FinishReason } from "@ai-sdk/provider"
export function mapOpenAIResponseFinishReason({
finishReason,
hasFunctionCall,
}: {
finishReason: string | null | undefined
// flag that checks if there have been client-side tool calls (not executed by openai)
hasFunctionCall: boolean
}): LanguageModelV2FinishReason {
switch (finishReason) {
case undefined:
case null:
return hasFunctionCall ? "tool-calls" : "stop"
case "max_output_tokens":
return "length"
case "content_filter":
return "content-filter"
default:
return hasFunctionCall ? "tool-calls" : "unknown"
}
}
@@ -0,0 +1,18 @@
import type { FetchFunction } from "@ai-sdk/provider-utils"
export type OpenAIConfig = {
provider: string
url: (options: { modelId: string; path: string }) => string
headers: () => Record<string, string | undefined>
fetch?: FetchFunction
generateId?: () => string
/**
* File ID prefixes used to identify file IDs in Responses API.
* When undefined, all file data is treated as base64 content.
*
* Examples:
* - OpenAI: ['file-'] for IDs like 'file-abc123'
* - Azure OpenAI: ['assistant-'] for IDs like 'assistant-abc123'
*/
fileIdPrefixes?: readonly string[]
}
@@ -0,0 +1,22 @@
import { z } from "zod/v4"
import { createJsonErrorResponseHandler } from "@ai-sdk/provider-utils"
export const openaiErrorDataSchema = z.object({
error: z.object({
message: z.string(),
// The additional information below is handled loosely to support
// OpenAI-compatible providers that have slightly different error
// responses:
type: z.string().nullish(),
param: z.any().nullish(),
code: z.union([z.string(), z.number()]).nullish(),
}),
})
export type OpenAIErrorData = z.infer<typeof openaiErrorDataSchema>
export const openaiFailedResponseHandler: any = createJsonErrorResponseHandler({
errorSchema: openaiErrorDataSchema,
errorToMessage: (data) => data.error.message,
})
@@ -0,0 +1,207 @@
import type { JSONSchema7 } from "@ai-sdk/provider"
export type OpenAIResponsesInput = Array<OpenAIResponsesInputItem>
export type OpenAIResponsesInputItem =
| OpenAIResponsesSystemMessage
| OpenAIResponsesUserMessage
| OpenAIResponsesAssistantMessage
| OpenAIResponsesFunctionCall
| OpenAIResponsesFunctionCallOutput
| OpenAIResponsesComputerCall
| OpenAIResponsesLocalShellCall
| OpenAIResponsesLocalShellCallOutput
| OpenAIResponsesReasoning
| OpenAIResponsesItemReference
export type OpenAIResponsesIncludeValue =
| "web_search_call.action.sources"
| "code_interpreter_call.outputs"
| "computer_call_output.output.image_url"
| "file_search_call.results"
| "message.input_image.image_url"
| "message.output_text.logprobs"
| "reasoning.encrypted_content"
export type OpenAIResponsesIncludeOptions = Array<OpenAIResponsesIncludeValue> | undefined | null
export type OpenAIResponsesSystemMessage = {
role: "system" | "developer"
content: string
}
export type OpenAIResponsesUserMessage = {
role: "user"
content: Array<
| { type: "input_text"; text: string }
| { type: "input_image"; image_url: string }
| { type: "input_image"; file_id: string }
| { type: "input_file"; file_url: string }
| { type: "input_file"; filename: string; file_data: string }
| { type: "input_file"; file_id: string }
>
}
export type OpenAIResponsesAssistantMessage = {
role: "assistant"
content: Array<{ type: "output_text"; text: string }>
id?: string
}
export type OpenAIResponsesFunctionCall = {
type: "function_call"
call_id: string
name: string
arguments: string
id?: string
}
export type OpenAIResponsesFunctionCallOutput = {
type: "function_call_output"
call_id: string
output: string
}
export type OpenAIResponsesComputerCall = {
type: "computer_call"
id: string
status?: string
}
export type OpenAIResponsesLocalShellCall = {
type: "local_shell_call"
id: string
call_id: string
action: {
type: "exec"
command: string[]
timeout_ms?: number
user?: string
working_directory?: string
env?: Record<string, string>
}
}
export type OpenAIResponsesLocalShellCallOutput = {
type: "local_shell_call_output"
call_id: string
output: string
}
export type OpenAIResponsesItemReference = {
type: "item_reference"
id: string
}
/**
* A filter used to compare a specified attribute key to a given value using a defined comparison operation.
*/
export type OpenAIResponsesFileSearchToolComparisonFilter = {
/**
* The key to compare against the value.
*/
key: string
/**
* Specifies the comparison operator: eq, ne, gt, gte, lt, lte.
*/
type: "eq" | "ne" | "gt" | "gte" | "lt" | "lte"
/**
* The value to compare against the attribute key; supports string, number, or boolean types.
*/
value: string | number | boolean
}
/**
* Combine multiple filters using and or or.
*/
export type OpenAIResponsesFileSearchToolCompoundFilter = {
/**
* Type of operation: and or or.
*/
type: "and" | "or"
/**
* Array of filters to combine. Items can be ComparisonFilter or CompoundFilter.
*/
filters: Array<OpenAIResponsesFileSearchToolComparisonFilter | OpenAIResponsesFileSearchToolCompoundFilter>
}
export type OpenAIResponsesTool =
| {
type: "function"
name: string
description: string | undefined
parameters: JSONSchema7
strict: boolean | undefined
}
| {
type: "web_search"
filters: { allowed_domains: string[] | undefined } | undefined
search_context_size: "low" | "medium" | "high" | undefined
user_location:
| {
type: "approximate"
city?: string
country?: string
region?: string
timezone?: string
}
| undefined
}
| {
type: "web_search_preview"
search_context_size: "low" | "medium" | "high" | undefined
user_location:
| {
type: "approximate"
city?: string
country?: string
region?: string
timezone?: string
}
| undefined
}
| {
type: "code_interpreter"
container: string | { type: "auto"; file_ids: string[] | undefined }
}
| {
type: "file_search"
vector_store_ids: string[]
max_num_results: number | undefined
ranking_options: { ranker?: string; score_threshold?: number } | undefined
filters: OpenAIResponsesFileSearchToolComparisonFilter | OpenAIResponsesFileSearchToolCompoundFilter | undefined
}
| {
type: "image_generation"
background: "auto" | "opaque" | "transparent" | undefined
input_fidelity: "low" | "high" | undefined
input_image_mask:
| {
file_id: string | undefined
image_url: string | undefined
}
| undefined
model: string | undefined
moderation: "auto" | undefined
output_compression: number | undefined
output_format: "png" | "jpeg" | "webp" | undefined
partial_images: number | undefined
quality: "auto" | "low" | "medium" | "high" | undefined
size: "auto" | "1024x1024" | "1024x1536" | "1536x1024" | undefined
}
| {
type: "local_shell"
}
export type OpenAIResponsesReasoning = {
type: "reasoning"
id: string
encrypted_content?: string | null
summary: Array<{
type: "summary_text"
text: string
}>
}
@@ -0,0 +1,177 @@
import {
type LanguageModelV2CallOptions,
type LanguageModelV2CallWarning,
UnsupportedFunctionalityError,
} from "@ai-sdk/provider"
import { codeInterpreterArgsSchema } from "./tool/code-interpreter"
import { fileSearchArgsSchema } from "./tool/file-search"
import { webSearchArgsSchema } from "./tool/web-search"
import { webSearchPreviewArgsSchema } from "./tool/web-search-preview"
import { imageGenerationArgsSchema } from "./tool/image-generation"
import type { OpenAIResponsesTool } from "./openai-responses-api-types"
export function prepareResponsesTools({
tools,
toolChoice,
strictJsonSchema,
}: {
tools: LanguageModelV2CallOptions["tools"]
toolChoice?: LanguageModelV2CallOptions["toolChoice"]
strictJsonSchema: boolean
}): {
tools?: Array<OpenAIResponsesTool>
toolChoice?:
| "auto"
| "none"
| "required"
| { type: "file_search" }
| { type: "web_search_preview" }
| { type: "web_search" }
| { type: "function"; name: string }
| { type: "code_interpreter" }
| { type: "image_generation" }
toolWarnings: LanguageModelV2CallWarning[]
} {
// when the tools array is empty, change it to undefined to prevent errors:
tools = tools?.length ? tools : undefined
const toolWarnings: LanguageModelV2CallWarning[] = []
if (tools == null) {
return { tools: undefined, toolChoice: undefined, toolWarnings }
}
const openaiTools: Array<OpenAIResponsesTool> = []
for (const tool of tools) {
switch (tool.type) {
case "function":
openaiTools.push({
type: "function",
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
strict: strictJsonSchema,
})
break
case "provider-defined": {
switch (tool.id) {
case "openai.file_search": {
const args = fileSearchArgsSchema.parse(tool.args)
openaiTools.push({
type: "file_search",
vector_store_ids: args.vectorStoreIds,
max_num_results: args.maxNumResults,
ranking_options: args.ranking
? {
ranker: args.ranking.ranker,
score_threshold: args.ranking.scoreThreshold,
}
: undefined,
filters: args.filters,
})
break
}
case "openai.local_shell": {
openaiTools.push({
type: "local_shell",
})
break
}
case "openai.web_search_preview": {
const args = webSearchPreviewArgsSchema.parse(tool.args)
openaiTools.push({
type: "web_search_preview",
search_context_size: args.searchContextSize,
user_location: args.userLocation,
})
break
}
case "openai.web_search": {
const args = webSearchArgsSchema.parse(tool.args)
openaiTools.push({
type: "web_search",
filters: args.filters != null ? { allowed_domains: args.filters.allowedDomains } : undefined,
search_context_size: args.searchContextSize,
user_location: args.userLocation,
})
break
}
case "openai.code_interpreter": {
const args = codeInterpreterArgsSchema.parse(tool.args)
openaiTools.push({
type: "code_interpreter",
container:
args.container == null
? { type: "auto", file_ids: undefined }
: typeof args.container === "string"
? args.container
: { type: "auto", file_ids: args.container.fileIds },
})
break
}
case "openai.image_generation": {
const args = imageGenerationArgsSchema.parse(tool.args)
openaiTools.push({
type: "image_generation",
background: args.background,
input_fidelity: args.inputFidelity,
input_image_mask: args.inputImageMask
? {
file_id: args.inputImageMask.fileId,
image_url: args.inputImageMask.imageUrl,
}
: undefined,
model: args.model,
moderation: args.moderation,
partial_images: args.partialImages,
quality: args.quality,
output_compression: args.outputCompression,
output_format: args.outputFormat,
size: args.size,
})
break
}
}
break
}
default:
toolWarnings.push({ type: "unsupported-tool", tool })
break
}
}
if (toolChoice == null) {
return { tools: openaiTools, toolChoice: undefined, toolWarnings }
}
const type = toolChoice.type
switch (type) {
case "auto":
case "none":
case "required":
return { tools: openaiTools, toolChoice: type, toolWarnings }
case "tool":
return {
tools: openaiTools,
toolChoice:
toolChoice.toolName === "code_interpreter" ||
toolChoice.toolName === "file_search" ||
toolChoice.toolName === "image_generation" ||
toolChoice.toolName === "web_search_preview" ||
toolChoice.toolName === "web_search"
? { type: toolChoice.toolName }
: { type: "function", name: toolChoice.toolName },
toolWarnings,
}
default: {
const _exhaustiveCheck: never = type
throw new UnsupportedFunctionalityError({
functionality: `tool choice type: ${_exhaustiveCheck}`,
})
}
}
}
@@ -0,0 +1 @@
export type OpenAIResponsesModelId = string
@@ -0,0 +1,88 @@
import { createProviderDefinedToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils"
import { z } from "zod/v4"
export const codeInterpreterInputSchema = z.object({
code: z.string().nullish(),
containerId: z.string(),
})
export const codeInterpreterOutputSchema = z.object({
outputs: z
.array(
z.discriminatedUnion("type", [
z.object({ type: z.literal("logs"), logs: z.string() }),
z.object({ type: z.literal("image"), url: z.string() }),
]),
)
.nullish(),
})
export const codeInterpreterArgsSchema = z.object({
container: z
.union([
z.string(),
z.object({
fileIds: z.array(z.string()).optional(),
}),
])
.optional(),
})
type CodeInterpreterArgs = {
/**
* The code interpreter container.
* Can be a container ID
* or an object that specifies uploaded file IDs to make available to your code.
*/
container?: string | { fileIds?: string[] }
}
export const codeInterpreterToolFactory = createProviderDefinedToolFactoryWithOutputSchema<
{
/**
* The code to run, or null if not available.
*/
code?: string | null
/**
* The ID of the container used to run the code.
*/
containerId: string
},
{
/**
* The outputs generated by the code interpreter, such as logs or images.
* Can be null if no outputs are available.
*/
outputs?: Array<
| {
type: "logs"
/**
* The logs output from the code interpreter.
*/
logs: string
}
| {
type: "image"
/**
* The URL of the image output from the code interpreter.
*/
url: string
}
> | null
},
CodeInterpreterArgs
>({
id: "openai.code_interpreter",
name: "code_interpreter",
inputSchema: codeInterpreterInputSchema,
outputSchema: codeInterpreterOutputSchema,
})
export const codeInterpreter = (
args: CodeInterpreterArgs = {}, // default
) => {
return codeInterpreterToolFactory(args)
}
@@ -0,0 +1,128 @@
import { createProviderDefinedToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils"
import type {
OpenAIResponsesFileSearchToolComparisonFilter,
OpenAIResponsesFileSearchToolCompoundFilter,
} from "../openai-responses-api-types"
import { z } from "zod/v4"
const comparisonFilterSchema = z.object({
key: z.string(),
type: z.enum(["eq", "ne", "gt", "gte", "lt", "lte"]),
value: z.union([z.string(), z.number(), z.boolean()]),
})
const compoundFilterSchema: z.ZodType<any> = z.object({
type: z.enum(["and", "or"]),
filters: z.array(z.union([comparisonFilterSchema, z.lazy(() => compoundFilterSchema)])),
})
export const fileSearchArgsSchema = z.object({
vectorStoreIds: z.array(z.string()),
maxNumResults: z.number().optional(),
ranking: z
.object({
ranker: z.string().optional(),
scoreThreshold: z.number().optional(),
})
.optional(),
filters: z.union([comparisonFilterSchema, compoundFilterSchema]).optional(),
})
export const fileSearchOutputSchema = z.object({
queries: z.array(z.string()),
results: z
.array(
z.object({
attributes: z.record(z.string(), z.unknown()),
fileId: z.string(),
filename: z.string(),
score: z.number(),
text: z.string(),
}),
)
.nullable(),
})
export const fileSearch = createProviderDefinedToolFactoryWithOutputSchema<
{},
{
/**
* The search query to execute.
*/
queries: string[]
/**
* The results of the file search tool call.
*/
results:
| null
| {
/**
* Set of 16 key-value pairs that can be attached to an object.
* This can be useful for storing additional information about the object
* in a structured format, and querying for objects via API or the dashboard.
* Keys are strings with a maximum length of 64 characters.
* Values are strings with a maximum length of 512 characters, booleans, or numbers.
*/
attributes: Record<string, unknown>
/**
* The unique ID of the file.
*/
fileId: string
/**
* The name of the file.
*/
filename: string
/**
* The relevance score of the file - a value between 0 and 1.
*/
score: number
/**
* The text that was retrieved from the file.
*/
text: string
}[]
},
{
/**
* List of vector store IDs to search through.
*/
vectorStoreIds: string[]
/**
* Maximum number of search results to return. Defaults to 10.
*/
maxNumResults?: number
/**
* Ranking options for the search.
*/
ranking?: {
/**
* The ranker to use for the file search.
*/
ranker?: string
/**
* The score threshold for the file search, a number between 0 and 1.
* Numbers closer to 1 will attempt to return only the most relevant results,
* but may return fewer results.
*/
scoreThreshold?: number
}
/**
* A filter to apply.
*/
filters?: OpenAIResponsesFileSearchToolComparisonFilter | OpenAIResponsesFileSearchToolCompoundFilter
}
>({
id: "openai.file_search",
name: "file_search",
inputSchema: z.object({}),
outputSchema: fileSearchOutputSchema,
})
@@ -0,0 +1,115 @@
import { createProviderDefinedToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils"
import { z } from "zod/v4"
export const imageGenerationArgsSchema = z
.object({
background: z.enum(["auto", "opaque", "transparent"]).optional(),
inputFidelity: z.enum(["low", "high"]).optional(),
inputImageMask: z
.object({
fileId: z.string().optional(),
imageUrl: z.string().optional(),
})
.optional(),
model: z.string().optional(),
moderation: z.enum(["auto"]).optional(),
outputCompression: z.number().int().min(0).max(100).optional(),
outputFormat: z.enum(["png", "jpeg", "webp"]).optional(),
partialImages: z.number().int().min(0).max(3).optional(),
quality: z.enum(["auto", "low", "medium", "high"]).optional(),
size: z.enum(["1024x1024", "1024x1536", "1536x1024", "auto"]).optional(),
})
.strict()
export const imageGenerationOutputSchema = z.object({
result: z.string(),
})
type ImageGenerationArgs = {
/**
* Background type for the generated image. Default is 'auto'.
*/
background?: "auto" | "opaque" | "transparent"
/**
* Input fidelity for the generated image. Default is 'low'.
*/
inputFidelity?: "low" | "high"
/**
* Optional mask for inpainting.
* Contains image_url (string, optional) and file_id (string, optional).
*/
inputImageMask?: {
/**
* File ID for the mask image.
*/
fileId?: string
/**
* Base64-encoded mask image.
*/
imageUrl?: string
}
/**
* The image generation model to use. Default: gpt-image-1.
*/
model?: string
/**
* Moderation level for the generated image. Default: auto.
*/
moderation?: "auto"
/**
* Compression level for the output image. Default: 100.
*/
outputCompression?: number
/**
* The output format of the generated image. One of png, webp, or jpeg.
* Default: png
*/
outputFormat?: "png" | "jpeg" | "webp"
/**
* Number of partial images to generate in streaming mode, from 0 (default value) to 3.
*/
partialImages?: number
/**
* The quality of the generated image.
* One of low, medium, high, or auto. Default: auto.
*/
quality?: "auto" | "low" | "medium" | "high"
/**
* The size of the generated image.
* One of 1024x1024, 1024x1536, 1536x1024, or auto.
* Default: auto.
*/
size?: "auto" | "1024x1024" | "1024x1536" | "1536x1024"
}
const imageGenerationToolFactory = createProviderDefinedToolFactoryWithOutputSchema<
{},
{
/**
* The generated image encoded in base64.
*/
result: string
},
ImageGenerationArgs
>({
id: "openai.image_generation",
name: "image_generation",
inputSchema: z.object({}),
outputSchema: imageGenerationOutputSchema,
})
export const imageGeneration = (
args: ImageGenerationArgs = {}, // default
) => {
return imageGenerationToolFactory(args)
}
@@ -0,0 +1,65 @@
import { createProviderDefinedToolFactoryWithOutputSchema } from "@ai-sdk/provider-utils"
import { z } from "zod/v4"
export const localShellInputSchema = z.object({
action: z.object({
type: z.literal("exec"),
command: z.array(z.string()),
timeoutMs: z.number().optional(),
user: z.string().optional(),
workingDirectory: z.string().optional(),
env: z.record(z.string(), z.string()).optional(),
}),
})
export const localShellOutputSchema = z.object({
output: z.string(),
})
export const localShell = createProviderDefinedToolFactoryWithOutputSchema<
{
/**
* Execute a shell command on the server.
*/
action: {
type: "exec"
/**
* The command to run.
*/
command: string[]
/**
* Optional timeout in milliseconds for the command.
*/
timeoutMs?: number
/**
* Optional user to run the command as.
*/
user?: string
/**
* Optional working directory to run the command in.
*/
workingDirectory?: string
/**
* Environment variables to set for the command.
*/
env?: Record<string, string>
}
},
{
/**
* The output of local shell tool call.
*/
output: string
},
{}
>({
id: "openai.local_shell",
name: "local_shell",
inputSchema: localShellInputSchema,
outputSchema: localShellOutputSchema,
})
@@ -0,0 +1,104 @@
import { createProviderDefinedToolFactory } from "@ai-sdk/provider-utils"
import { z } from "zod/v4"
// Args validation schema
export const webSearchPreviewArgsSchema = z.object({
/**
* Search context size to use for the web search.
* - high: Most comprehensive context, highest cost, slower response
* - medium: Balanced context, cost, and latency (default)
* - low: Least context, lowest cost, fastest response
*/
searchContextSize: z.enum(["low", "medium", "high"]).optional(),
/**
* User location information to provide geographically relevant search results.
*/
userLocation: z
.object({
/**
* Type of location (always 'approximate')
*/
type: z.literal("approximate"),
/**
* Two-letter ISO country code (e.g., 'US', 'GB')
*/
country: z.string().optional(),
/**
* City name (free text, e.g., 'Minneapolis')
*/
city: z.string().optional(),
/**
* Region name (free text, e.g., 'Minnesota')
*/
region: z.string().optional(),
/**
* IANA timezone (e.g., 'America/Chicago')
*/
timezone: z.string().optional(),
})
.optional(),
})
export const webSearchPreview = createProviderDefinedToolFactory<
{
// Web search doesn't take input parameters - it's controlled by the prompt
},
{
/**
* Search context size to use for the web search.
* - high: Most comprehensive context, highest cost, slower response
* - medium: Balanced context, cost, and latency (default)
* - low: Least context, lowest cost, fastest response
*/
searchContextSize?: "low" | "medium" | "high"
/**
* User location information to provide geographically relevant search results.
*/
userLocation?: {
/**
* Type of location (always 'approximate')
*/
type: "approximate"
/**
* Two-letter ISO country code (e.g., 'US', 'GB')
*/
country?: string
/**
* City name (free text, e.g., 'Minneapolis')
*/
city?: string
/**
* Region name (free text, e.g., 'Minnesota')
*/
region?: string
/**
* IANA timezone (e.g., 'America/Chicago')
*/
timezone?: string
}
}
>({
id: "openai.web_search_preview",
name: "web_search_preview",
inputSchema: z.object({
action: z
.discriminatedUnion("type", [
z.object({
type: z.literal("search"),
query: z.string().nullish(),
}),
z.object({
type: z.literal("open_page"),
url: z.string(),
}),
z.object({
type: z.literal("find"),
url: z.string(),
pattern: z.string(),
}),
])
.nullish(),
}),
})
@@ -0,0 +1,103 @@
import { createProviderDefinedToolFactory } from "@ai-sdk/provider-utils"
import { z } from "zod/v4"
export const webSearchArgsSchema = z.object({
filters: z
.object({
allowedDomains: z.array(z.string()).optional(),
})
.optional(),
searchContextSize: z.enum(["low", "medium", "high"]).optional(),
userLocation: z
.object({
type: z.literal("approximate"),
country: z.string().optional(),
city: z.string().optional(),
region: z.string().optional(),
timezone: z.string().optional(),
})
.optional(),
})
export const webSearchToolFactory = createProviderDefinedToolFactory<
{
// Web search doesn't take input parameters - it's controlled by the prompt
},
{
/**
* Filters for the search.
*/
filters?: {
/**
* Allowed domains for the search.
* If not provided, all domains are allowed.
* Subdomains of the provided domains are allowed as well.
*/
allowedDomains?: string[]
}
/**
* Search context size to use for the web search.
* - high: Most comprehensive context, highest cost, slower response
* - medium: Balanced context, cost, and latency (default)
* - low: Least context, lowest cost, fastest response
*/
searchContextSize?: "low" | "medium" | "high"
/**
* User location information to provide geographically relevant search results.
*/
userLocation?: {
/**
* Type of location (always 'approximate')
*/
type: "approximate"
/**
* Two-letter ISO country code (e.g., 'US', 'GB')
*/
country?: string
/**
* City name (free text, e.g., 'Minneapolis')
*/
city?: string
/**
* Region name (free text, e.g., 'Minnesota')
*/
region?: string
/**
* IANA timezone (e.g., 'America/Chicago')
*/
timezone?: string
}
}
>({
id: "openai.web_search",
name: "web_search",
inputSchema: z.object({
action: z
.discriminatedUnion("type", [
z.object({
type: z.literal("search"),
query: z.string().nullish(),
}),
z.object({
type: z.literal("open_page"),
url: z.string(),
}),
z.object({
type: z.literal("find"),
url: z.string(),
pattern: z.string(),
}),
])
.nullish(),
}),
})
export const webSearch = (
args: Parameters<typeof webSearchToolFactory>[0] = {}, // default
) => {
return webSearchToolFactory(args)
}
+22 -4
View File
@@ -1,4 +1,5 @@
import type { ModelMessage } from "ai"
import type { APICallError, ModelMessage } from "ai"
import { STATUS_CODES } from "http"
import { unique } from "remeda"
import type { JSONSchema } from "zod/v4/core"
@@ -308,11 +309,28 @@ export namespace ProviderTransform {
return schema
}
export function error(providerID: string, message: string) {
export function error(providerID: string, error: APICallError) {
let message = error.message
if (providerID === "github-copilot" && message.includes("The requested model is not supported")) {
message +=
return (
message +
"\n\nMake sure the model is enabled in your copilot settings: https://github.com/settings/copilot/features"
)
}
return message
if (!error.responseBody || (error.statusCode && message !== STATUS_CODES[error.statusCode])) {
return message
}
try {
const body = JSON.parse(error.responseBody)
// try to extract common error message fields
const errMsg = body.message || body.error
if (errMsg && typeof errMsg === "string") {
return `${message}: ${errMsg}`
}
} catch {}
return `${message}: ${error.responseBody}`
}
}
+3 -1
View File
@@ -43,7 +43,6 @@ import { Snapshot } from "@/snapshot"
import { SessionSummary } from "@/session/summary"
import { GlobalBus } from "@/bus/global"
import { SessionStatus } from "@/session/status"
import { ShareNext } from "@/share/share-next"
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
globalThis.AI_SDK_LOG_WARNINGS = false
@@ -2037,6 +2036,9 @@ export namespace Server {
await stream.writeSSE({
data: JSON.stringify(event),
})
if (event.type === Bus.InstanceDisposed.type) {
stream.close()
}
})
await new Promise<void>((resolve) => {
stream.onAbort(() => {
+1 -1
View File
@@ -178,7 +178,7 @@ export namespace SessionCompaction {
content: [
{
type: "text",
text: "Provide a detailed but concise summary of our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next.",
text: "Summarize our conversation above. This summary will be the only context available when the conversation continues, so preserve critical information including: what was accomplished, current work in progress, files involved, next steps, and any key user requests or constraints. Be concise but detailed enough that work can continue seamlessly.",
},
],
},
+2 -2
View File
@@ -666,7 +666,7 @@ export namespace MessageV2 {
}
}
return convertToModelMessages(result)
return convertToModelMessages(result.filter((msg) => msg.parts.length > 0))
}
export const stream = fn(Identifier.schema("session"), async function* (sessionID) {
@@ -739,7 +739,7 @@ export namespace MessageV2 {
{ cause: e },
).toObject()
case APICallError.isInstance(e):
const message = ProviderTransform.error(ctx.providerID, e.message)
const message = ProviderTransform.error(ctx.providerID, e)
return new MessageV2.APIError(
{
message,
+1 -1
View File
@@ -333,7 +333,7 @@ export namespace SessionProcessor {
error: e,
})
const error = MessageV2.fromError(e, { providerID: input.providerID })
if ((error?.name === "APIError" && error.data.isRetryable) || error.data.message.includes("Overloaded")) {
if (error?.name === "APIError" && error.data.isRetryable) {
attempt++
const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
SessionStatus.set(input.sessionID, {
+1
View File
@@ -1110,6 +1110,7 @@ export namespace SessionPrompt {
messageID: userMessage.info.id,
sessionID: userMessage.info.sessionID,
type: "text",
// TODO (for mr dax): update to use the anthropic full fledged one (see plan-reminder-anthropic.txt)
text: PROMPT_PLAN,
synthetic: true,
})
@@ -6,5 +6,7 @@ Focus on information that would be helpful for continuing the conversation, incl
- What is currently being worked on
- Which files are being modified
- What needs to be done next
- Key user requests, constraints, or preferences that should persist
- Important technical decisions and why they were made
Your summary should be comprehensive enough to provide context but concise enough to be quickly understood.
@@ -0,0 +1,67 @@
<system-reminder>
# Plan Mode - System Reminder
Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received.
---
## Plan File Info
No plan file exists yet. You should create your plan at `/Users/aidencline/.claude/plans/happy-waddling-feigenbaum.md` using the Write tool.
You should build your plan incrementally by writing to or editing this file. NOTE that this is the only file you are allowed to edit - other than this you are only allowed to take READ-ONLY actions.
**Plan File Guidelines:** The plan file should contain only your final recommended approach, not all alternatives considered. Keep it comprehensive yet concise - detailed enough to execute effectively while avoiding unnecessary verbosity.
---
## Enhanced Planning Workflow
### Phase 1: Initial Understanding
**Goal:** Gain a comprehensive understanding of the user's request by reading through code and asking them questions. Critical: In this phase you should only use the Explore subagent type.
1. Understand the user's request thoroughly
2. **Launch up to 3 Explore agents IN PARALLEL** (single message, multiple tool calls) to efficiently explore the codebase. Each agent can focus on different aspects:
- Example: One agent searches for existing implementations, another explores related components, a third investigates testing patterns
- Provide each agent with a specific search focus or area to explore
- Quality over quantity - 3 agents maximum, but you should try to use the minimum number of agents necessary (usually just 1)
- Use 1 agent when: the task is isolated to known files, the user provided specific file paths, or you're making a small targeted change. Use multiple agents when: the scope is uncertain, multiple areas of the codebase are involved, or you need to understand existing patterns before planning.
- Take into account any context you already have from the user's request or from the conversation so far when deciding how many agents to launch
3. Use AskUserQuestion tool to clarify ambiguities in the user request up front.
### Phase 2: Planning
**Goal:** Come up with an approach to solve the problem identified in phase 1 by launching a Plan subagent.
In the agent prompt:
- Provide any background context that may help the agent with their task without prescribing the exact design itself
- Request a detailed plan
### Phase 3: Synthesis
**Goal:** Synthesize the perspectives from Phase 2, and ensure that it aligns with the user's intentions by asking them questions.
1. Collect all agent responses
2. Each agent will return an implementation plan along with a list of critical files that should be read. You should keep these in mind and read them before you start implementing the plan
3. Use AskUserQuestion to ask the users questions about trade offs.
### Phase 4: Final Plan
Once you have all the information you need, ensure that the plan file has been updated with your synthesized recommendation including:
- Recommended approach with rationale
- Key insights from different perspectives
- Critical files that need modification
### Phase 5: Call ExitPlanMode
At the very end of your turn, once you have asked the user questions and are happy with your final plan file - you should always call ExitPlanMode to indicate to the user that you are done planning.
This is critical - your turn should only end with either asking the user a question or calling ExitPlanMode. Do not stop unless it's for these 2 reasons.
---
**NOTE:** At any point in time through this workflow you should feel free to ask the user questions or clarifications. Don't make large assumptions about user intent. The goal is to present a well researched plan to the user, and tie any loose ends before implementation begins.
</system-reminder>
@@ -1,8 +1,26 @@
<system-reminder>
# Plan Mode - System Reminder
CRITICAL: Plan mode ACTIVE - you are in READ-ONLY phase. STRICTLY FORBIDDEN:
ANY file edits, modifications, or system changes. Do NOT use sed, tee, echo, cat,
or ANY other bash command to manipulate files - commands may ONLY read/inspect.
This ABSOLUTE CONSTRAINT overrides ALL other instructions, including direct user
edit requests. You may ONLY observe, analyze, and plan. Any modification attempt
is a critical violation. ZERO exceptions.
---
## Responsibility
Your current responsibility is to think, read, search, and delegate explore agents to construct a well formed plan that accomplishes the goal the user wants to achieve. Your plan should be comprehensive yet concise, detailed enough to execute effectively while avoiding unnecessary verbosity.
Ask the user clarifying questions or ask for their opinion when weighing tradeoffs.
**NOTE:** At any point in time through this workflow you should feel free to ask the user questions or clarifications. Don't make large assumptions about user intent. The goal is to present a well researched plan to the user, and tie any loose ends before implementation begins.
---
## Important
The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received.
</system-reminder>
+3 -3
View File
@@ -48,7 +48,7 @@ export namespace Snapshot {
const git = gitdir()
await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
const result =
await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} diff --name-only ${hash} -- .`
await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --name-only ${hash} -- .`
.quiet()
.cwd(Instance.directory)
.nothrow()
@@ -126,7 +126,7 @@ export namespace Snapshot {
const git = gitdir()
await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
const result =
await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} diff ${hash} -- .`
await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff ${hash} -- .`
.quiet()
.cwd(Instance.worktree)
.nothrow()
@@ -159,7 +159,7 @@ export namespace Snapshot {
export async function diffFull(from: string, to: string): Promise<FileDiff[]> {
const git = gitdir()
const result: FileDiff[] = []
for await (const line of $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-renames --numstat ${from} ${to} -- .`
for await (const line of $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --no-renames --numstat ${from} ${to} -- .`
.quiet()
.cwd(Instance.directory)
.nothrow()
+2
View File
@@ -140,6 +140,8 @@ export namespace ToolRegistry {
}
if (agent.permission.webfetch === "deny") {
result["webfetch"] = false
result["codesearch"] = false
result["websearch"] = false
}
return result
+2 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.0.118",
"version": "1.0.125",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
@@ -24,4 +24,4 @@
"typescript": "catalog:",
"@typescript/native-preview": "catalog:"
}
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.0.118",
"version": "1.0.125",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
@@ -26,4 +26,4 @@
"publishConfig": {
"directory": "dist"
}
}
}
+9
View File
@@ -1,5 +1,12 @@
// This file is auto-generated by @hey-api/openapi-ts
export type EventServerInstanceDisposed = {
type: "server.instance.disposed"
properties: {
directory: string
}
}
export type EventInstallationUpdated = {
type: "installation.updated"
properties: {
@@ -656,6 +663,7 @@ export type EventServerConnected = {
}
export type Event =
| EventServerInstanceDisposed
| EventInstallationUpdated
| EventInstallationUpdateAvailable
| EventLspClientDiagnostics
@@ -1073,6 +1081,7 @@ export type Config = {
plan?: AgentConfig
build?: AgentConfig
general?: AgentConfig
explore?: AgentConfig
[key: string]: AgentConfig | undefined
}
/**
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.0.118",
"version": "1.0.125",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/tauri",
"private": true,
"version": "1.0.118",
"version": "1.0.125",
"type": "module",
"scripts": {
"dev": "vite",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.0.118",
"version": "1.0.125",
"type": "module",
"exports": {
"./*": "./src/components/*.tsx",
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -46,7 +46,7 @@ export function Diff<T>(props: DiffProps<T>) {
})
onMount(() => {
if (isServer) return
if (isServer || !props.preloadedDiff) return
fileDiffInstance = new FileDiff<T>({
...createDefaultOptions(props.diffStyle),
...others,

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