Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
585378cba0 | ||
|
|
8cd8393339 | ||
|
|
b184b2fb73 | ||
|
|
c88c2da9be | ||
|
|
9b04081ae0 | ||
|
|
7d2d87fa2c | ||
|
|
787f37b382 | ||
|
|
8fa1af851c | ||
|
|
73bc3e704e | ||
|
|
8d2feed30e | ||
|
|
2d8d4e5dee | ||
|
|
b3784588ae | ||
|
|
104d52bc38 | ||
|
|
dff1fe2d28 | ||
|
|
72ab4260ee | ||
|
|
9e9b4a0555 | ||
|
|
e53192889c | ||
|
|
23bbfb3d15 | ||
|
|
37da005a01 | ||
|
|
8b708242f1 | ||
|
|
339d2dcb98 | ||
|
|
bbc8678164 | ||
|
|
a1d54475fe | ||
|
|
55c601d13a | ||
|
|
9115fac4c4 | ||
|
|
cfcb2c1fd8 | ||
|
|
221fc62135 | ||
|
|
faaef45384 | ||
|
|
2d18d80ac3 | ||
|
|
e0e07c5d48 | ||
|
|
281f9e6236 | ||
|
|
f88903a901 | ||
|
|
ad425a6a6a | ||
|
|
e635d37027 | ||
|
|
97081484d5 | ||
|
|
e451504496 | ||
|
|
53211c5d37 | ||
|
|
98b6817e20 | ||
|
|
f54d5377a4 | ||
|
|
a576fdb5e4 | ||
|
|
ae53f876f1 | ||
|
|
a7beba5aa9 | ||
|
|
e9ef72c20f | ||
|
|
fa1ac7bc95 | ||
|
|
c82ab649e2 | ||
|
|
abc7eed92b | ||
|
|
1670d220da | ||
|
|
ddc4e34731 | ||
|
|
af99d83709 | ||
|
|
ed0c0d90be | ||
|
|
e1dd9c4ccb | ||
|
|
4657fa823f | ||
|
|
1d589c7ac7 | ||
|
|
6b5a0fb261 | ||
|
|
6d93a7bf55 | ||
|
|
4ca7ab6be8 | ||
|
|
713d996b9f | ||
|
|
aa16610021 | ||
|
|
d98568fe7e | ||
|
|
1da3550c4d | ||
|
|
0c48e6a116 | ||
|
|
ef266b2c74 | ||
|
|
0a1cdc7a58 | ||
|
|
2dec956a17 | ||
|
|
ef8388f0ee | ||
|
|
e5c5b5e872 | ||
|
|
a1c9a1b8c5 | ||
|
|
76b012139a | ||
|
|
02e5a19242 | ||
|
|
af967648cb | ||
|
|
504a668a26 | ||
|
|
5efb1c7b2d | ||
|
|
fd973d242e | ||
|
|
c3d8672753 | ||
|
|
fe8ef041f6 | ||
|
|
c841de947e | ||
|
|
825dfd48b1 | ||
|
|
923d114ffa | ||
|
|
b157fd10a7 | ||
|
|
67ebe68160 | ||
|
|
7b63c14154 | ||
|
|
cdc11cde2e |
@@ -5,6 +5,9 @@ on:
|
|||||||
- cron: "0 */12 * * *"
|
- cron: "0 */12 * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
LOOKBACK_HOURS: 4
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-docs:
|
update-docs:
|
||||||
if: github.repository == 'sst/opencode'
|
if: github.repository == 'sst/opencode'
|
||||||
@@ -25,9 +28,9 @@ jobs:
|
|||||||
- name: Get recent commits
|
- name: Get recent commits
|
||||||
id: commits
|
id: commits
|
||||||
run: |
|
run: |
|
||||||
COMMITS=$(git log --since="4 hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "")
|
COMMITS=$(git log --since="${{ env.LOOKBACK_HOURS }} hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "")
|
||||||
if [ -z "$COMMITS" ]; then
|
if [ -z "$COMMITS" ]; then
|
||||||
echo "No commits in the last 4 hours"
|
echo "No commits in the last ${{ env.LOOKBACK_HOURS }} hours"
|
||||||
echo "has_commits=false" >> $GITHUB_OUTPUT
|
echo "has_commits=false" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
echo "has_commits=true" >> $GITHUB_OUTPUT
|
echo "has_commits=true" >> $GITHUB_OUTPUT
|
||||||
@@ -47,7 +50,7 @@ jobs:
|
|||||||
model: opencode/gpt-5.2
|
model: opencode/gpt-5.2
|
||||||
agent: docs
|
agent: docs
|
||||||
prompt: |
|
prompt: |
|
||||||
Review the following commits from the last 4 hours and identify any new features that may need documentation.
|
Review the following commits from the last ${{ env.LOOKBACK_HOURS }} hours and identify any new features that may need documentation.
|
||||||
|
|
||||||
<recent_commits>
|
<recent_commits>
|
||||||
${{ steps.commits.outputs.list }}
|
${{ steps.commits.outputs.list }}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ on:
|
|||||||
- cron: "30 1 * * *" # Daily at 1:30 AM
|
- cron: "30 1 * * *" # Daily at 1:30 AM
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
DAYS_BEFORE_STALE: 90
|
||||||
|
DAYS_BEFORE_CLOSE: 7
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -13,17 +17,17 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v10
|
- uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
days-before-stale: 90
|
days-before-stale: ${{ env.DAYS_BEFORE_STALE }}
|
||||||
days-before-close: 7
|
days-before-close: ${{ env.DAYS_BEFORE_CLOSE }}
|
||||||
stale-issue-label: "stale"
|
stale-issue-label: "stale"
|
||||||
close-issue-message: |
|
close-issue-message: |
|
||||||
[automated] Closing due to 90+ days of inactivity.
|
[automated] Closing due to ${{ env.DAYS_BEFORE_STALE }}+ days of inactivity.
|
||||||
|
|
||||||
Feel free to reopen if you still need this!
|
Feel free to reopen if you still need this!
|
||||||
stale-issue-message: |
|
stale-issue-message: |
|
||||||
[automated] This issue has had no activity for 90 days.
|
[automated] This issue has had no activity for ${{ env.DAYS_BEFORE_STALE }} days.
|
||||||
|
|
||||||
It will be closed in 7 days if there's no new activity.
|
It will be closed in ${{ env.DAYS_BEFORE_CLOSE }} days if there's no new activity.
|
||||||
remove-stale-when-updated: true
|
remove-stale-when-updated: true
|
||||||
exempt-issue-labels: "pinned,security,feature-request,on-hold"
|
exempt-issue-labels: "pinned,security,feature-request,on-hold"
|
||||||
start-date: "2025-12-27"
|
start-date: "2025-12-27"
|
||||||
|
|||||||
@@ -32,3 +32,4 @@ jobs:
|
|||||||
./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }}
|
./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }}
|
||||||
env:
|
env:
|
||||||
ZED_EXTENSIONS_PAT: ${{ secrets.ZED_EXTENSIONS_PAT }}
|
ZED_EXTENSIONS_PAT: ${{ secrets.ZED_EXTENSIONS_PAT }}
|
||||||
|
ZED_PR_PAT: ${{ secrets.ZED_PR_PAT }}
|
||||||
|
|||||||
@@ -20,3 +20,7 @@ opencode.json
|
|||||||
a.out
|
a.out
|
||||||
target
|
target
|
||||||
.scripts
|
.scripts
|
||||||
|
|
||||||
|
# Local dev files
|
||||||
|
opencode-dev
|
||||||
|
logs/
|
||||||
|
|||||||
@@ -34,6 +34,36 @@ Want to take on an issue? Leave a comment and a maintainer may assign it to you
|
|||||||
bun dev
|
bun dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Running against a different directory
|
||||||
|
|
||||||
|
By default, `bun dev` runs OpenCode in the `packages/opencode` directory. To run it against a different directory or repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun dev <directory>
|
||||||
|
```
|
||||||
|
|
||||||
|
To run OpenCode in the root of the opencode repo itself:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun dev .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building a "localcode"
|
||||||
|
|
||||||
|
To compile a standalone executable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./packages/opencode/script/build.ts --single
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./packages/opencode/dist/opencode-<platform>/bin/opencode
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<platform>` with your platform (e.g., `darwin-arm64`, `linux-x64`).
|
||||||
|
|
||||||
- Core pieces:
|
- Core pieces:
|
||||||
- `packages/opencode`: OpenCode core business logic & server.
|
- `packages/opencode`: OpenCode core business logic & server.
|
||||||
- `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
|
- `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
|
||||||
|
|||||||
@@ -185,3 +185,4 @@
|
|||||||
| 2025-12-27 | 1,371,771 (+19,360) | 1,238,236 (+10,621) | 2,610,007 (+29,981) |
|
| 2025-12-27 | 1,371,771 (+19,360) | 1,238,236 (+10,621) | 2,610,007 (+29,981) |
|
||||||
| 2025-12-28 | 1,390,388 (+18,617) | 1,245,690 (+7,454) | 2,636,078 (+26,071) |
|
| 2025-12-28 | 1,390,388 (+18,617) | 1,245,690 (+7,454) | 2,636,078 (+26,071) |
|
||||||
| 2025-12-29 | 1,415,560 (+25,172) | 1,257,101 (+11,411) | 2,672,661 (+36,583) |
|
| 2025-12-29 | 1,415,560 (+25,172) | 1,257,101 (+11,411) | 2,672,661 (+36,583) |
|
||||||
|
| 2025-12-30 | 1,445,450 (+29,890) | 1,272,689 (+15,588) | 2,718,139 (+45,478) |
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "2.0.0",
|
"@ai-sdk/anthropic": "2.0.0",
|
||||||
"@ai-sdk/openai": "2.0.2",
|
"@ai-sdk/openai": "2.0.2",
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -173,13 +173,14 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/app": "workspace:*",
|
"@opencode-ai/app": "workspace:*",
|
||||||
"@solid-primitives/storage": "catalog:",
|
"@solid-primitives/storage": "catalog:",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tauri-apps/plugin-dialog": "~2",
|
"@tauri-apps/plugin-dialog": "~2",
|
||||||
"@tauri-apps/plugin-http": "~2",
|
"@tauri-apps/plugin-http": "~2",
|
||||||
|
"@tauri-apps/plugin-notification": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"@tauri-apps/plugin-os": "~2",
|
"@tauri-apps/plugin-os": "~2",
|
||||||
"@tauri-apps/plugin-process": "~2",
|
"@tauri-apps/plugin-process": "~2",
|
||||||
@@ -200,7 +201,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
@@ -229,7 +230,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -245,7 +246,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -255,18 +256,17 @@
|
|||||||
"@agentclientprotocol/sdk": "0.5.1",
|
"@agentclientprotocol/sdk": "0.5.1",
|
||||||
"@ai-sdk/amazon-bedrock": "3.0.57",
|
"@ai-sdk/amazon-bedrock": "3.0.57",
|
||||||
"@ai-sdk/anthropic": "2.0.56",
|
"@ai-sdk/anthropic": "2.0.56",
|
||||||
"@ai-sdk/azure": "2.0.73",
|
"@ai-sdk/azure": "2.0.82",
|
||||||
"@ai-sdk/cerebras": "1.0.33",
|
"@ai-sdk/cerebras": "1.0.33",
|
||||||
"@ai-sdk/cohere": "2.0.21",
|
"@ai-sdk/cohere": "2.0.21",
|
||||||
"@ai-sdk/deepinfra": "1.0.30",
|
"@ai-sdk/deepinfra": "1.0.30",
|
||||||
"@ai-sdk/gateway": "2.0.23",
|
"@ai-sdk/gateway": "2.0.23",
|
||||||
"@ai-sdk/google": "2.0.44",
|
"@ai-sdk/google": "2.0.49",
|
||||||
"@ai-sdk/google-vertex": "3.0.81",
|
"@ai-sdk/google-vertex": "3.0.81",
|
||||||
"@ai-sdk/groq": "2.0.33",
|
"@ai-sdk/groq": "2.0.33",
|
||||||
"@ai-sdk/mcp": "0.0.8",
|
|
||||||
"@ai-sdk/mistral": "2.0.26",
|
"@ai-sdk/mistral": "2.0.26",
|
||||||
"@ai-sdk/openai": "2.0.71",
|
"@ai-sdk/openai": "2.0.71",
|
||||||
"@ai-sdk/openai-compatible": "1.0.27",
|
"@ai-sdk/openai-compatible": "1.0.29",
|
||||||
"@ai-sdk/perplexity": "2.0.22",
|
"@ai-sdk/perplexity": "2.0.22",
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "2.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.19",
|
"@ai-sdk/provider-utils": "3.0.19",
|
||||||
@@ -284,8 +284,8 @@
|
|||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
"@openrouter/ai-sdk-provider": "1.5.2",
|
"@openrouter/ai-sdk-provider": "1.5.2",
|
||||||
"@opentui/core": "0.1.63",
|
"@opentui/core": "0.1.67",
|
||||||
"@opentui/solid": "0.1.63",
|
"@opentui/solid": "0.1.67",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@solid-primitives/event-bus": "1.1.2",
|
"@solid-primitives/event-bus": "1.1.2",
|
||||||
@@ -347,7 +347,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
@@ -367,7 +367,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hey-api/openapi-ts": "0.88.1",
|
"@hey-api/openapi-ts": "0.88.1",
|
||||||
"@tsconfig/node22": "catalog:",
|
"@tsconfig/node22": "catalog:",
|
||||||
@@ -378,7 +378,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -391,7 +391,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -429,7 +429,7 @@
|
|||||||
},
|
},
|
||||||
"packages/util": {
|
"packages/util": {
|
||||||
"name": "@opencode-ai/util",
|
"name": "@opencode-ai/util",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -440,7 +440,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -541,7 +541,7 @@
|
|||||||
|
|
||||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
|
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.0", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uyyaO4KhxoIKZztREqLPh+6/K3ZJx/rp72JKoUEL9/kC+vfQTThUfPnY/bUryUpcnawx8IY/tSoYNOi/8PCv7w=="],
|
||||||
|
|
||||||
"@ai-sdk/azure": ["@ai-sdk/azure@2.0.73", "", { "dependencies": { "@ai-sdk/openai": "2.0.71", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LpAg3Ak/V3WOemBu35Qbx9jfQfApsHNXX9p3bXVsnRu3XXi1QQUt5gMOCIb4znPonz+XnHenIDZMBwdsb1TfRQ=="],
|
"@ai-sdk/azure": ["@ai-sdk/azure@2.0.82", "", { "dependencies": { "@ai-sdk/openai": "2.0.80", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Bpab51ETBB4adZC1xGMYsryL/CB8j1sA+t5aDqhRv3t3WRLTxhaBDcFKtQTIuxiEQTFosz9Q2xQqdfBvQm5jHw=="],
|
||||||
|
|
||||||
"@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2gSSS/7kunIwMdC4td5oWsUAzoLw84ccGpz6wQbxVnrb1iWnrEnKa5tRBduaP6IXpzLWsu8wME3+dQhZy+gT7w=="],
|
"@ai-sdk/cerebras": ["@ai-sdk/cerebras@1.0.33", "", { "dependencies": { "@ai-sdk/openai-compatible": "1.0.29", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2gSSS/7kunIwMdC4td5oWsUAzoLw84ccGpz6wQbxVnrb1iWnrEnKa5tRBduaP6IXpzLWsu8wME3+dQhZy+gT7w=="],
|
||||||
|
|
||||||
@@ -551,14 +551,12 @@
|
|||||||
|
|
||||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-qmX7afPRszUqG5hryHF3UN8ITPIRSGmDW6VYCmByzjoUkgm3MekzSx2hMV1wr0P+llDeuXb378SjqUfpvWJulg=="],
|
"@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-qmX7afPRszUqG5hryHF3UN8ITPIRSGmDW6VYCmByzjoUkgm3MekzSx2hMV1wr0P+llDeuXb378SjqUfpvWJulg=="],
|
||||||
|
|
||||||
"@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": ["@ai-sdk/google@2.0.49", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-efwKk4mOV0SpumUaQskeYABk37FJPmEYwoDJQEjyLRmGSjtHRe9P5Cwof5ffLvaFav2IaJpBGEz98pyTs7oNWA=="],
|
||||||
|
|
||||||
"@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/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/groq": ["@ai-sdk/groq@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FWGl7xNr88NBveao3y9EcVWYUt9ABPrwLFY7pIutSNgaTf32vgvyhREobaMrLU4Scr5G/2tlNqOPZ5wkYMaZig=="],
|
"@ai-sdk/groq": ["@ai-sdk/groq@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-FWGl7xNr88NBveao3y9EcVWYUt9ABPrwLFY7pIutSNgaTf32vgvyhREobaMrLU4Scr5G/2tlNqOPZ5wkYMaZig=="],
|
||||||
|
|
||||||
"@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=="],
|
|
||||||
|
|
||||||
"@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.26", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jxDB++4WI1wEx5ONNBI+VbkmYJOYIuS8UQY13/83UGRaiW7oB/WHiH4ETe6KzbKpQPB3XruwTJQjUMsMfKyTXA=="],
|
"@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.26", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-jxDB++4WI1wEx5ONNBI+VbkmYJOYIuS8UQY13/83UGRaiW7oB/WHiH4ETe6KzbKpQPB3XruwTJQjUMsMfKyTXA=="],
|
||||||
|
|
||||||
"@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="],
|
"@ai-sdk/openai": ["@ai-sdk/openai@2.0.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-D4zYz2uR90aooKQvX1XnS00Z7PkbrcY+snUvPfm5bCabTG7bzLrVtD56nJ5bSaZG8lmuOMfXpyiEEArYLyWPpw=="],
|
||||||
@@ -1195,21 +1193,21 @@
|
|||||||
|
|
||||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||||
|
|
||||||
"@opentui/core": ["@opentui/core@0.1.63", "", { "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.63", "@opentui/core-darwin-x64": "0.1.63", "@opentui/core-linux-arm64": "0.1.63", "@opentui/core-linux-x64": "0.1.63", "@opentui/core-win32-arm64": "0.1.63", "@opentui/core-win32-x64": "0.1.63", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-m4xZQTNCnHXWUWCnGvacJ3Gts1H2aMwP5V/puAG77SDb51jm4W/QOyqAAdgeSakkb9II+8FfUpApX7sfwRXPUg=="],
|
"@opentui/core": ["@opentui/core@0.1.67", "", { "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.67", "@opentui/core-darwin-x64": "0.1.67", "@opentui/core-linux-arm64": "0.1.67", "@opentui/core-linux-x64": "0.1.67", "@opentui/core-win32-arm64": "0.1.67", "@opentui/core-win32-x64": "0.1.67", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-zmfyA10QUbzT6ohacPoHmGiYzuJrDSCfQWRWrKtao0BrHj9bii73qWy3V/eR4ibVueoRREwxJs5GlBOSvK6IoA=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.63", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jKCThZGiiublKkP/hMtDtl1MLCw5NU0hMNJdEYvz1WLT9bzliWf6Kb7MIDAmk32XlbQW8/RHdp+hGyGDXK62OQ=="],
|
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.67", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LtOcTlFD+kO7neItmkiF77H8cnjTYzBOZe8JQGwRSt9aaCke3UzMvLxmQnj4BP/kPC3hi9V6NRnFdptz0sJZIQ=="],
|
||||||
|
|
||||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.63", "", { "os": "darwin", "cpu": "x64" }, "sha512-rfNxynHzJpxN9i+SAMnn1NToEc8rYj64BsOxY78JNsm4Gg1Js1uyMaawwh2WbdGknFy4cDXS9QwkUMdMcfnjiw=="],
|
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.67", "", { "os": "darwin", "cpu": "x64" }, "sha512-9i+awVWgpEVqZhFLaLq8usNGyCiyT5QxMLy6eH7JmRic79S34u23HfxiniGRtdYh3aqpm9SbLzo60v0nRIUkCA=="],
|
||||||
|
|
||||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.63", "", { "os": "linux", "cpu": "arm64" }, "sha512-wG9d6mHWWKZGrzxYS4c+BrcEGXBv/MYBUPSyjP/lD0CxT+X3h6CYhI317JkRyMNfh3vI9CpAKGFTOFvrTTHimQ=="],
|
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.67", "", { "os": "linux", "cpu": "arm64" }, "sha512-WLjnTM3Ig//SRo0FUZYZJ5TITVbR6dKDVg6axU2D+sMoUzJMBP/Xo04q/TvZ3wP764Yca9l7oVMKWDxHlygyjQ=="],
|
||||||
|
|
||||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.63", "", { "os": "linux", "cpu": "x64" }, "sha512-TKSzFv4BgWW3RB/iZmq5qxTR4/tRaXo8IZNnVR+LFzShbPOqhUi466AByy9SUmCxD8uYjmMDFYfKtkCy0AnAwA=="],
|
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.67", "", { "os": "linux", "cpu": "x64" }, "sha512-5UbZ/TqWi/DAmHIZL4NvhdpgTwglszRiddkRiQ8cT0IbnE4lutd4XxWUWcLKwsNT1YJv32TtcGWkuthluLiriQ=="],
|
||||||
|
|
||||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.63", "", { "os": "win32", "cpu": "arm64" }, "sha512-CBWPyPognERP0Mq4eC1q01Ado2C2WU+BLTgMdhyt+E2P4w8rPhJ2kCt2MNxO66vQUiynspmZkgjQr0II/VjxWA=="],
|
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.67", "", { "os": "win32", "cpu": "arm64" }, "sha512-KNam5rObhN8/U9+GVVuvtAlGXp3MfdMHnw4W2P6YH7xp8HTsLvABUT91SJEyJ/ktVe9e1itLDG2fDHSoA5NbUg=="],
|
||||||
|
|
||||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.63", "", { "os": "win32", "cpu": "x64" }, "sha512-qEp6h//FrT+TQiiHm87wZWUwqTPTqIy1ZD+8R+VCUK+usoQiOAD2SqrYnM7W8JkCMGn5/TKm/GaKLyx/qlK4VA=="],
|
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.67", "", { "os": "win32", "cpu": "x64" }, "sha512-740lkOw42zLNh9YfahXjCwV2DS/amH2uMDh3tCADDCLckrMhemIhqArXDiMlalDxDqYspoaZCpBsFVsG9dMS6A=="],
|
||||||
|
|
||||||
"@opentui/solid": ["@opentui/solid@0.1.63", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.63", "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-Gccln4qRucAoaoQEZ4NPAHvGmVYzU/8aKCLG8EPgwCKTcpUzlqYt4357cDHq4cnCNOcXOC06hTz/0pK9r0dqXA=="],
|
"@opentui/solid": ["@opentui/solid@0.1.67", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.67", "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-dVNq0+PJIdNb63D0T7vcbyVF/ZvLCihGvivTU50zDOzd0Sk5prbrIfpG8+DjMErFubXfdZQvdy/PqFdtw0rjtQ=="],
|
||||||
|
|
||||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||||
|
|
||||||
@@ -1709,6 +1707,8 @@
|
|||||||
|
|
||||||
"@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.4", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-/i4U/9za3mrytTgfRn5RHneKubZE/dwRmshYwyMvNRlkWjvu1m4Ma72kcbVJMZFGXpkbl+qLyWMGrihtWB76Zg=="],
|
"@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.4", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-/i4U/9za3mrytTgfRn5RHneKubZE/dwRmshYwyMvNRlkWjvu1m4Ma72kcbVJMZFGXpkbl+qLyWMGrihtWB76Zg=="],
|
||||||
|
|
||||||
|
"@tauri-apps/plugin-notification": ["@tauri-apps/plugin-notification@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew=="],
|
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="],
|
"@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="],
|
||||||
@@ -3901,22 +3901,16 @@
|
|||||||
|
|
||||||
"@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/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/openai": ["@ai-sdk/openai@2.0.80", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.18" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tNHuraF11db+8xJEDBoU9E3vMcpnHFKRhnLQ3DQX2LnEzfPB9DksZ8rE+yVuDN1WRW9cm2OWAhgHFgVKs7ICuw=="],
|
||||||
|
|
||||||
"@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/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
"@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
||||||
|
|
||||||
"@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
"@ai-sdk/deepinfra/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
||||||
|
|
||||||
"@ai-sdk/google/@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=="],
|
|
||||||
|
|
||||||
"@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/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/google-vertex/@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=="],
|
"@ai-sdk/google-vertex/@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=="],
|
||||||
|
|
||||||
"@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/@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=="],
|
"@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=="],
|
||||||
@@ -4309,7 +4303,7 @@
|
|||||||
|
|
||||||
"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=="],
|
"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=="],
|
||||||
|
|
||||||
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.27", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bpYruxVLhrTbVH6CCq48zMJNeHu6FmHtEedl9FXckEgcIEAi036idFhJlcRwC1jNCwlacbzb8dPD7OAH1EKJaQ=="],
|
"opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
|
||||||
|
|
||||||
@@ -4913,8 +4907,6 @@
|
|||||||
|
|
||||||
"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/@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=="],
|
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
||||||
|
|
||||||
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
|
||||||
|
|||||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1766996594,
|
"lastModified": 1767026758,
|
||||||
"narHash": "sha256-SosfgQSqVmOkqVgNYJnxW5FvoIQX4grOcpIKNrIwz4o=",
|
"narHash": "sha256-7fsac/f7nh/VaKJ/qm3I338+wAJa/3J57cOGpXi0Sbg=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "0744ef1b047f07d31d9962d757ffe38ec14a4d41",
|
"rev": "346dd96ad74dc4457a9db9de4f4f57dab2e5731d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"aarch64-darwin"
|
"aarch64-darwin"
|
||||||
"x86_64-darwin"
|
"x86_64-darwin"
|
||||||
];
|
];
|
||||||
lib = nixpkgs.lib;
|
inherit (nixpkgs) lib;
|
||||||
forEachSystem = lib.genAttrs systems;
|
forEachSystem = lib.genAttrs systems;
|
||||||
pkgsFor = system: nixpkgs.legacyPackages.${system};
|
pkgsFor = system: nixpkgs.legacyPackages.${system};
|
||||||
packageJson = builtins.fromJSON (builtins.readFile ./packages/opencode/package.json);
|
packageJson = builtins.fromJSON (builtins.readFile ./packages/opencode/package.json);
|
||||||
@@ -70,12 +70,12 @@
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
default = mkPackage {
|
default = mkPackage {
|
||||||
version = packageJson.version;
|
inherit (packageJson) version;
|
||||||
src = ./.;
|
src = ./.;
|
||||||
scripts = ./nix/scripts;
|
scripts = ./nix/scripts;
|
||||||
target = bunTarget.${system};
|
target = bunTarget.${system};
|
||||||
modelsDev = "${modelsDev.${system}}/dist/_api.json";
|
modelsDev = "${modelsDev.${system}}/dist/_api.json";
|
||||||
mkNodeModules = mkNodeModules;
|
inherit mkNodeModules;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": "sha256-2i/QMBzp9MalOXur36mXaDDU8R9G/0dODCODEQOnaCU="
|
"nodeModules": "sha256-2Wbnxy9SPcZkO03Sis3uiypPXa87jc5TzKbo6PvMlxY="
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-10
@@ -1,18 +1,26 @@
|
|||||||
{ hash, lib, stdenvNoCC, bun, cacert, curl }:
|
{
|
||||||
|
hash,
|
||||||
|
lib,
|
||||||
|
stdenvNoCC,
|
||||||
|
bun,
|
||||||
|
cacert,
|
||||||
|
curl,
|
||||||
|
}:
|
||||||
args:
|
args:
|
||||||
stdenvNoCC.mkDerivation {
|
stdenvNoCC.mkDerivation {
|
||||||
pname = "opencode-node_modules";
|
pname = "opencode-node_modules";
|
||||||
version = args.version;
|
inherit (args) version src;
|
||||||
src = args.src;
|
|
||||||
|
|
||||||
impureEnvVars =
|
impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ [
|
||||||
lib.fetchers.proxyImpureEnvVars
|
"GIT_PROXY_COMMAND"
|
||||||
++ [
|
"SOCKS_SERVER"
|
||||||
"GIT_PROXY_COMMAND"
|
];
|
||||||
"SOCKS_SERVER"
|
|
||||||
];
|
|
||||||
|
|
||||||
nativeBuildInputs = [ bun cacert curl ];
|
nativeBuildInputs = [
|
||||||
|
bun
|
||||||
|
cacert
|
||||||
|
curl
|
||||||
|
];
|
||||||
|
|
||||||
dontConfigure = true;
|
dontConfigure = true;
|
||||||
|
|
||||||
|
|||||||
+10
-7
@@ -1,7 +1,13 @@
|
|||||||
{ lib, stdenvNoCC, bun, ripgrep, makeBinaryWrapper }:
|
{
|
||||||
|
lib,
|
||||||
|
stdenvNoCC,
|
||||||
|
bun,
|
||||||
|
ripgrep,
|
||||||
|
makeBinaryWrapper,
|
||||||
|
}:
|
||||||
args:
|
args:
|
||||||
let
|
let
|
||||||
scripts = args.scripts;
|
inherit (args) scripts;
|
||||||
mkModules =
|
mkModules =
|
||||||
attrs:
|
attrs:
|
||||||
args.mkNodeModules (
|
args.mkNodeModules (
|
||||||
@@ -14,13 +20,10 @@ let
|
|||||||
in
|
in
|
||||||
stdenvNoCC.mkDerivation (finalAttrs: {
|
stdenvNoCC.mkDerivation (finalAttrs: {
|
||||||
pname = "opencode";
|
pname = "opencode";
|
||||||
version = args.version;
|
inherit (args) version src;
|
||||||
|
|
||||||
src = args.src;
|
|
||||||
|
|
||||||
node_modules = mkModules {
|
node_modules = mkModules {
|
||||||
version = finalAttrs.version;
|
inherit (finalAttrs) version src;
|
||||||
src = finalAttrs.src;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
|
|||||||
@@ -31,9 +31,13 @@ for (const [name, wasmPath] of byName) {
|
|||||||
next = next.replaceAll("tree-sitter.wasm", mainWasm).replaceAll("web-tree-sitter/tree-sitter.wasm", mainWasm)
|
next = next.replaceAll("tree-sitter.wasm", mainWasm).replaceAll("web-tree-sitter/tree-sitter.wasm", mainWasm)
|
||||||
|
|
||||||
// Collapse any relative prefixes before absolute store paths (e.g., "../../../..//nix/store/...")
|
// Collapse any relative prefixes before absolute store paths (e.g., "../../../..//nix/store/...")
|
||||||
|
const nixStorePrefix = process.env.NIX_STORE || "/nix/store"
|
||||||
next = next.replace(/(\.\/)+/g, "./")
|
next = next.replace(/(\.\/)+/g, "./")
|
||||||
next = next.replace(/(\.\.\/)+\/?(\/nix\/store[^"']+)/g, "/$2")
|
next = next.replace(
|
||||||
next = next.replace(/(["'])\/{2,}(\/nix\/store[^"']+)(["'])/g, "$1/$2$3")
|
new RegExp(`(\\.\\.\\/)+\\/{1,2}(${nixStorePrefix.replace(/^\//, "").replace(/\//g, "\\/")}[^"']+)`, "g"),
|
||||||
next = next.replace(/(["'])\/\/(nix\/store[^"']+)(["'])/g, "$1/$2$3")
|
"/$2",
|
||||||
|
)
|
||||||
|
next = next.replace(new RegExp(`(["'])\\/{2,}(\\/${nixStorePrefix.replace(/\//g, "\\/")}[^"']+)(["'])`, "g"), "$1$2$3")
|
||||||
|
next = next.replace(new RegExp(`(["'])\\/\\/(${nixStorePrefix.replace(/\//g, "\\/")}[^"']+)(["'])`, "g"), "$1$2$3")
|
||||||
|
|
||||||
if (next !== content) fs.writeFileSync(file, next)
|
if (next !== content) fs.writeFileSync(file, next)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
+47
-33
@@ -1,5 +1,5 @@
|
|||||||
import "@/index.css"
|
import "@/index.css"
|
||||||
import { ErrorBoundary, Show } from "solid-js"
|
import { ErrorBoundary, Show, type ParentProps } from "solid-js"
|
||||||
import { Router, Route, Navigate } from "@solidjs/router"
|
import { Router, Route, Navigate } from "@solidjs/router"
|
||||||
import { MetaProvider } from "@solidjs/meta"
|
import { MetaProvider } from "@solidjs/meta"
|
||||||
import { Font } from "@opencode-ai/ui/font"
|
import { Font } from "@opencode-ai/ui/font"
|
||||||
@@ -12,6 +12,7 @@ import { ThemeProvider } from "@opencode-ai/ui/theme"
|
|||||||
import { GlobalSyncProvider } from "@/context/global-sync"
|
import { GlobalSyncProvider } from "@/context/global-sync"
|
||||||
import { LayoutProvider } from "@/context/layout"
|
import { LayoutProvider } from "@/context/layout"
|
||||||
import { GlobalSDKProvider } from "@/context/global-sdk"
|
import { GlobalSDKProvider } from "@/context/global-sdk"
|
||||||
|
import { ServerProvider, useServer } from "@/context/server"
|
||||||
import { TerminalProvider } from "@/context/terminal"
|
import { TerminalProvider } from "@/context/terminal"
|
||||||
import { PromptProvider } from "@/context/prompt"
|
import { PromptProvider } from "@/context/prompt"
|
||||||
import { NotificationProvider } from "@/context/notification"
|
import { NotificationProvider } from "@/context/notification"
|
||||||
@@ -30,7 +31,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = iife(() => {
|
const defaultServerUrl = iife(() => {
|
||||||
const param = new URLSearchParams(document.location.search).get("url")
|
const param = new URLSearchParams(document.location.search).get("url")
|
||||||
if (param) return param
|
if (param) return param
|
||||||
|
|
||||||
@@ -42,6 +43,15 @@ const url = iife(() => {
|
|||||||
return window.location.origin
|
return window.location.origin
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function ServerKey(props: ParentProps) {
|
||||||
|
const server = useServer()
|
||||||
|
return (
|
||||||
|
<Show when={server.url} keyed>
|
||||||
|
{props.children}
|
||||||
|
</Show>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
@@ -52,38 +62,42 @@ export function App() {
|
|||||||
<MarkedProvider>
|
<MarkedProvider>
|
||||||
<DiffComponentProvider component={Diff}>
|
<DiffComponentProvider component={Diff}>
|
||||||
<CodeComponentProvider component={Code}>
|
<CodeComponentProvider component={Code}>
|
||||||
<GlobalSDKProvider url={url}>
|
<ServerProvider defaultUrl={defaultServerUrl}>
|
||||||
<GlobalSyncProvider>
|
<ServerKey>
|
||||||
<LayoutProvider>
|
<GlobalSDKProvider>
|
||||||
<NotificationProvider>
|
<GlobalSyncProvider>
|
||||||
<Router
|
<LayoutProvider>
|
||||||
root={(props) => (
|
<NotificationProvider>
|
||||||
<CommandProvider>
|
<Router
|
||||||
<Layout>{props.children}</Layout>
|
root={(props) => (
|
||||||
</CommandProvider>
|
<CommandProvider>
|
||||||
)}
|
<Layout>{props.children}</Layout>
|
||||||
>
|
</CommandProvider>
|
||||||
<Route path="/" component={Home} />
|
|
||||||
<Route path="/:dir" component={DirectoryLayout}>
|
|
||||||
<Route path="/" component={() => <Navigate href="session" />} />
|
|
||||||
<Route
|
|
||||||
path="/session/:id?"
|
|
||||||
component={(p) => (
|
|
||||||
<Show when={p.params.id ?? "new"} keyed>
|
|
||||||
<TerminalProvider>
|
|
||||||
<PromptProvider>
|
|
||||||
<Session />
|
|
||||||
</PromptProvider>
|
|
||||||
</TerminalProvider>
|
|
||||||
</Show>
|
|
||||||
)}
|
)}
|
||||||
/>
|
>
|
||||||
</Route>
|
<Route path="/" component={Home} />
|
||||||
</Router>
|
<Route path="/:dir" component={DirectoryLayout}>
|
||||||
</NotificationProvider>
|
<Route path="/" component={() => <Navigate href="session" />} />
|
||||||
</LayoutProvider>
|
<Route
|
||||||
</GlobalSyncProvider>
|
path="/session/:id?"
|
||||||
</GlobalSDKProvider>
|
component={(p) => (
|
||||||
|
<Show when={p.params.id ?? "new"} keyed>
|
||||||
|
<TerminalProvider>
|
||||||
|
<PromptProvider>
|
||||||
|
<Session />
|
||||||
|
</PromptProvider>
|
||||||
|
</TerminalProvider>
|
||||||
|
</Show>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
</Router>
|
||||||
|
</NotificationProvider>
|
||||||
|
</LayoutProvider>
|
||||||
|
</GlobalSyncProvider>
|
||||||
|
</GlobalSDKProvider>
|
||||||
|
</ServerKey>
|
||||||
|
</ServerProvider>
|
||||||
</CodeComponentProvider>
|
</CodeComponentProvider>
|
||||||
</DiffComponentProvider>
|
</DiffComponentProvider>
|
||||||
</MarkedProvider>
|
</MarkedProvider>
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
|
import { List } from "@opencode-ai/ui/list"
|
||||||
|
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||||
|
import { createMemo } from "solid-js"
|
||||||
|
import { useGlobalSDK } from "@/context/global-sdk"
|
||||||
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
|
|
||||||
|
interface DialogSelectDirectoryProps {
|
||||||
|
title?: string
|
||||||
|
multiple?: boolean
|
||||||
|
onSelect: (result: string | string[] | null) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
||||||
|
const sync = useGlobalSync()
|
||||||
|
const sdk = useGlobalSDK()
|
||||||
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
const home = createMemo(() => sync.data.path.home)
|
||||||
|
const root = createMemo(() => sync.data.path.home || sync.data.path.directory)
|
||||||
|
|
||||||
|
function join(base: string | undefined, rel: string) {
|
||||||
|
const b = (base ?? "").replace(/[\\/]+$/, "")
|
||||||
|
const r = rel.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")
|
||||||
|
if (!b) return r
|
||||||
|
if (!r) return b
|
||||||
|
return b + "/" + r
|
||||||
|
}
|
||||||
|
|
||||||
|
function display(rel: string) {
|
||||||
|
const full = join(root(), rel)
|
||||||
|
const h = home()
|
||||||
|
if (!h) return full
|
||||||
|
if (full === h) return "~"
|
||||||
|
if (full.startsWith(h + "/") || full.startsWith(h + "\\")) {
|
||||||
|
return "~" + full.slice(h.length)
|
||||||
|
}
|
||||||
|
return full
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeQuery(query: string) {
|
||||||
|
const h = home()
|
||||||
|
|
||||||
|
if (!query) return query
|
||||||
|
if (query.startsWith("~/")) return query.slice(2)
|
||||||
|
|
||||||
|
if (h) {
|
||||||
|
const lc = query.toLowerCase()
|
||||||
|
const hc = h.toLowerCase()
|
||||||
|
if (lc === hc || lc.startsWith(hc + "/") || lc.startsWith(hc + "\\")) {
|
||||||
|
return query.slice(h.length).replace(/^[\\/]+/, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchDirs(query: string) {
|
||||||
|
const directory = root()
|
||||||
|
if (!directory) return [] as string[]
|
||||||
|
|
||||||
|
const results = await sdk.client.find
|
||||||
|
.files({ directory, query, type: "directory", limit: 50 })
|
||||||
|
.then((x) => x.data ?? [])
|
||||||
|
.catch(() => [])
|
||||||
|
|
||||||
|
return results.map((x) => x.replace(/[\\/]+$/, ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
const directories = async (filter: string) => {
|
||||||
|
const query = normalizeQuery(filter.trim())
|
||||||
|
return fetchDirs(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve(rel: string) {
|
||||||
|
const absolute = join(root(), rel)
|
||||||
|
props.onSelect(props.multiple ? [absolute] : absolute)
|
||||||
|
dialog.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog title={props.title ?? "Open project"}>
|
||||||
|
<List
|
||||||
|
search={{ placeholder: "Search folders", autofocus: true }}
|
||||||
|
emptyMessage="No folders found"
|
||||||
|
items={directories}
|
||||||
|
key={(x) => x}
|
||||||
|
onSelect={(path) => {
|
||||||
|
if (!path) return
|
||||||
|
resolve(path)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(rel) => {
|
||||||
|
const path = display(rel)
|
||||||
|
return (
|
||||||
|
<div class="w-full flex items-center justify-between rounded-md">
|
||||||
|
<div class="flex items-center gap-x-3 grow min-w-0">
|
||||||
|
<FileIcon node={{ path: rel, type: "directory" }} class="shrink-0 size-4" />
|
||||||
|
<div class="flex items-center text-14-regular min-w-0">
|
||||||
|
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
|
||||||
|
{getDirectory(path)}
|
||||||
|
</span>
|
||||||
|
<span class="text-text-strong whitespace-nowrap">{getFilename(path)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</List>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import { createEffect, createMemo, onCleanup } from "solid-js"
|
||||||
|
import { createStore, reconcile } from "solid-js/store"
|
||||||
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
|
import { List } from "@opencode-ai/ui/list"
|
||||||
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
|
import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||||
|
import { useNavigate } from "@solidjs/router"
|
||||||
|
|
||||||
|
type ServerStatus = { healthy: boolean; version?: string }
|
||||||
|
|
||||||
|
async function checkHealth(url: string, fetch?: typeof globalThis.fetch): Promise<ServerStatus> {
|
||||||
|
const sdk = createOpencodeClient({
|
||||||
|
baseUrl: url,
|
||||||
|
fetch,
|
||||||
|
signal: AbortSignal.timeout(3000),
|
||||||
|
})
|
||||||
|
return sdk.global
|
||||||
|
.health()
|
||||||
|
.then((x) => ({ healthy: x.data?.healthy === true, version: x.data?.version }))
|
||||||
|
.catch(() => ({ healthy: false }))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogSelectServer() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const dialog = useDialog()
|
||||||
|
const server = useServer()
|
||||||
|
const platform = usePlatform()
|
||||||
|
const [store, setStore] = createStore({
|
||||||
|
url: "",
|
||||||
|
adding: false,
|
||||||
|
error: "",
|
||||||
|
status: {} as Record<string, ServerStatus | undefined>,
|
||||||
|
})
|
||||||
|
|
||||||
|
const items = createMemo(() => {
|
||||||
|
const current = server.url
|
||||||
|
const list = server.list
|
||||||
|
if (!current) return list
|
||||||
|
if (!list.includes(current)) return [current, ...list]
|
||||||
|
return [current, ...list.filter((x) => x !== current)]
|
||||||
|
})
|
||||||
|
|
||||||
|
const current = createMemo(() => items().find((x) => x === server.url) ?? items()[0])
|
||||||
|
|
||||||
|
const sortedItems = createMemo(() => {
|
||||||
|
const list = items()
|
||||||
|
if (!list.length) return list
|
||||||
|
const active = current()
|
||||||
|
const order = new Map(list.map((url, index) => [url, index] as const))
|
||||||
|
const rank = (value?: ServerStatus) => {
|
||||||
|
if (value?.healthy === true) return 0
|
||||||
|
if (value?.healthy === false) return 2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return list.slice().sort((a, b) => {
|
||||||
|
if (a === active) return -1
|
||||||
|
if (b === active) return 1
|
||||||
|
const diff = rank(store.status[a]) - rank(store.status[b])
|
||||||
|
if (diff !== 0) return diff
|
||||||
|
return (order.get(a) ?? 0) - (order.get(b) ?? 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
async function refreshHealth() {
|
||||||
|
const results: Record<string, ServerStatus> = {}
|
||||||
|
await Promise.all(
|
||||||
|
items().map(async (url) => {
|
||||||
|
results[url] = await checkHealth(url, platform.fetch)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
setStore("status", reconcile(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
items()
|
||||||
|
refreshHealth()
|
||||||
|
const interval = setInterval(refreshHealth, 10_000)
|
||||||
|
onCleanup(() => clearInterval(interval))
|
||||||
|
})
|
||||||
|
|
||||||
|
function select(value: string, persist?: boolean) {
|
||||||
|
if (!persist && store.status[value]?.healthy === false) return
|
||||||
|
dialog.close()
|
||||||
|
if (persist) {
|
||||||
|
server.add(value)
|
||||||
|
navigate("/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
server.setActive(value)
|
||||||
|
navigate("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit(e: SubmitEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
const value = normalizeServerUrl(store.url)
|
||||||
|
if (!value) return
|
||||||
|
|
||||||
|
setStore("adding", true)
|
||||||
|
setStore("error", "")
|
||||||
|
|
||||||
|
const result = await checkHealth(value, platform.fetch)
|
||||||
|
setStore("adding", false)
|
||||||
|
|
||||||
|
if (!result.healthy) {
|
||||||
|
setStore("error", "Could not connect to server")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setStore("url", "")
|
||||||
|
select(value, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog title="Servers" description="Switch which OpenCode server this app connects to.">
|
||||||
|
<div class="flex flex-col gap-4 pb-4">
|
||||||
|
<List
|
||||||
|
search={{ placeholder: "Search servers", autofocus: true }}
|
||||||
|
emptyMessage="No servers yet"
|
||||||
|
items={sortedItems}
|
||||||
|
key={(x) => x}
|
||||||
|
current={current()}
|
||||||
|
onSelect={(x) => {
|
||||||
|
if (x) select(x)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(i) => (
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-2 min-w-0 flex-1"
|
||||||
|
classList={{ "opacity-50": store.status[i]?.healthy === false }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
classList={{
|
||||||
|
"size-1.5 rounded-full shrink-0": true,
|
||||||
|
"bg-icon-success-base": store.status[i]?.healthy === true,
|
||||||
|
"bg-icon-critical-base": store.status[i]?.healthy === false,
|
||||||
|
"bg-border-weak-base": store.status[i] === undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span class="truncate">{serverDisplayName(i)}</span>
|
||||||
|
<span class="text-text-weak">{store.status[i]?.version}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
<div class="mt-6 px-3 flex flex-col gap-1.5">
|
||||||
|
<div class="px-3">
|
||||||
|
<h3 class="text-14-regular text-text-weak">Add a server</h3>
|
||||||
|
</div>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<div class="flex-1 min-w-0 h-auto">
|
||||||
|
<TextField
|
||||||
|
type="text"
|
||||||
|
label="Server URL"
|
||||||
|
hideLabel
|
||||||
|
placeholder="http://localhost:4096"
|
||||||
|
value={store.url}
|
||||||
|
onChange={(v) => {
|
||||||
|
setStore("url", v)
|
||||||
|
setStore("error", "")
|
||||||
|
}}
|
||||||
|
validationState={store.error ? "invalid" : "valid"}
|
||||||
|
error={store.error}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button type="submit" variant="secondary" icon="plus-small" size="large" disabled={store.adding}>
|
||||||
|
{store.adding ? "Checking..." : "Add"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -134,6 +134,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
imageAttachments: ImageAttachmentPart[]
|
imageAttachments: ImageAttachmentPart[]
|
||||||
mode: "normal" | "shell"
|
mode: "normal" | "shell"
|
||||||
applyingHistory: boolean
|
applyingHistory: boolean
|
||||||
|
killBuffer: string
|
||||||
}>({
|
}>({
|
||||||
popover: null,
|
popover: null,
|
||||||
historyIndex: -1,
|
historyIndex: -1,
|
||||||
@@ -143,6 +144,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
imageAttachments: [],
|
imageAttachments: [],
|
||||||
mode: "normal",
|
mode: "normal",
|
||||||
applyingHistory: false,
|
applyingHistory: false,
|
||||||
|
killBuffer: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
const MAX_HISTORY = 100
|
const MAX_HISTORY = 100
|
||||||
@@ -648,6 +650,77 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
setStore("popover", null)
|
setStore("popover", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setSelectionOffsets = (start: number, end: number) => {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection) return false
|
||||||
|
|
||||||
|
const length = promptLength(prompt.current())
|
||||||
|
const a = Math.max(0, Math.min(start, length))
|
||||||
|
const b = Math.max(0, Math.min(end, length))
|
||||||
|
const rangeStart = Math.min(a, b)
|
||||||
|
const rangeEnd = Math.max(a, b)
|
||||||
|
|
||||||
|
const range = document.createRange()
|
||||||
|
range.selectNodeContents(editorRef)
|
||||||
|
|
||||||
|
const setEdge = (edge: "start" | "end", offset: number) => {
|
||||||
|
let remaining = offset
|
||||||
|
const nodes = Array.from(editorRef.childNodes)
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
const length = getNodeLength(node)
|
||||||
|
const isText = node.nodeType === Node.TEXT_NODE
|
||||||
|
const isFile = node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).dataset.type === "file"
|
||||||
|
const isBreak = node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).tagName === "BR"
|
||||||
|
|
||||||
|
if (isText && remaining <= length) {
|
||||||
|
if (edge === "start") range.setStart(node, remaining)
|
||||||
|
if (edge === "end") range.setEnd(node, remaining)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((isFile || isBreak) && remaining <= length) {
|
||||||
|
if (edge === "start" && remaining === 0) range.setStartBefore(node)
|
||||||
|
if (edge === "start" && remaining > 0) range.setStartAfter(node)
|
||||||
|
if (edge === "end" && remaining === 0) range.setEndBefore(node)
|
||||||
|
if (edge === "end" && remaining > 0) range.setEndAfter(node)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining -= length
|
||||||
|
}
|
||||||
|
|
||||||
|
const last = editorRef.lastChild
|
||||||
|
if (!last) {
|
||||||
|
if (edge === "start") range.setStart(editorRef, 0)
|
||||||
|
if (edge === "end") range.setEnd(editorRef, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (edge === "start") range.setStartAfter(last)
|
||||||
|
if (edge === "end") range.setEndAfter(last)
|
||||||
|
}
|
||||||
|
|
||||||
|
setEdge("start", rangeStart)
|
||||||
|
setEdge("end", rangeEnd)
|
||||||
|
selection.removeAllRanges()
|
||||||
|
selection.addRange(range)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const replaceOffsets = (start: number, end: number, content: string) => {
|
||||||
|
if (!setSelectionOffsets(start, end)) return false
|
||||||
|
addPart({ type: "text", content, start: 0, end: 0 })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const killText = (start: number, end: number) => {
|
||||||
|
if (start === end) return
|
||||||
|
const current = prompt.current()
|
||||||
|
if (!current.every((part) => part.type === "text")) return
|
||||||
|
const text = current.map((part) => part.content).join("")
|
||||||
|
setStore("killBuffer", text.slice(start, end))
|
||||||
|
}
|
||||||
|
|
||||||
const abort = () =>
|
const abort = () =>
|
||||||
sdk.client.session
|
sdk.client.session
|
||||||
.abort({
|
.abort({
|
||||||
@@ -768,6 +841,164 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ctrl = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey
|
||||||
|
const alt = event.altKey && !event.metaKey && !event.ctrlKey && !event.shiftKey
|
||||||
|
|
||||||
|
if (ctrl && event.code === "KeyG") {
|
||||||
|
if (store.popover) {
|
||||||
|
setStore("popover", null)
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (working()) {
|
||||||
|
abort()
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctrl || alt) {
|
||||||
|
const { collapsed, cursorPosition, textLength } = getCaretState()
|
||||||
|
if (collapsed) {
|
||||||
|
const current = prompt.current()
|
||||||
|
const text = current.map((part) => ("content" in part ? part.content : "")).join("")
|
||||||
|
|
||||||
|
if (ctrl) {
|
||||||
|
if (event.code === "KeyA") {
|
||||||
|
const pos = text.lastIndexOf("\n", cursorPosition - 1) + 1
|
||||||
|
setCursorPosition(editorRef, pos)
|
||||||
|
event.preventDefault()
|
||||||
|
queueScroll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === "KeyE") {
|
||||||
|
const next = text.indexOf("\n", cursorPosition)
|
||||||
|
const pos = next === -1 ? textLength : next
|
||||||
|
setCursorPosition(editorRef, pos)
|
||||||
|
event.preventDefault()
|
||||||
|
queueScroll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === "KeyB") {
|
||||||
|
const pos = Math.max(0, cursorPosition - 1)
|
||||||
|
setCursorPosition(editorRef, pos)
|
||||||
|
event.preventDefault()
|
||||||
|
queueScroll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === "KeyF") {
|
||||||
|
const pos = Math.min(textLength, cursorPosition + 1)
|
||||||
|
setCursorPosition(editorRef, pos)
|
||||||
|
event.preventDefault()
|
||||||
|
queueScroll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === "KeyD") {
|
||||||
|
if (store.mode === "shell" && cursorPosition === 0 && textLength === 0) {
|
||||||
|
setStore("mode", "normal")
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (cursorPosition >= textLength) return
|
||||||
|
replaceOffsets(cursorPosition, cursorPosition + 1, "")
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === "KeyK") {
|
||||||
|
const next = text.indexOf("\n", cursorPosition)
|
||||||
|
const lineEnd = next === -1 ? textLength : next
|
||||||
|
const end = lineEnd === cursorPosition && lineEnd < textLength ? lineEnd + 1 : lineEnd
|
||||||
|
if (end === cursorPosition) return
|
||||||
|
killText(cursorPosition, end)
|
||||||
|
replaceOffsets(cursorPosition, end, "")
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === "KeyU") {
|
||||||
|
const start = text.lastIndexOf("\n", cursorPosition - 1) + 1
|
||||||
|
if (start === cursorPosition) return
|
||||||
|
killText(start, cursorPosition)
|
||||||
|
replaceOffsets(start, cursorPosition, "")
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === "KeyW") {
|
||||||
|
let start = cursorPosition
|
||||||
|
while (start > 0 && /\s/.test(text[start - 1])) start -= 1
|
||||||
|
while (start > 0 && !/\s/.test(text[start - 1])) start -= 1
|
||||||
|
if (start === cursorPosition) return
|
||||||
|
killText(start, cursorPosition)
|
||||||
|
replaceOffsets(start, cursorPosition, "")
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === "KeyY") {
|
||||||
|
if (!store.killBuffer) return
|
||||||
|
addPart({ type: "text", content: store.killBuffer, start: 0, end: 0 })
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === "KeyT") {
|
||||||
|
if (!current.every((part) => part.type === "text")) return
|
||||||
|
if (textLength < 2) return
|
||||||
|
if (cursorPosition === 0) return
|
||||||
|
|
||||||
|
const atEnd = cursorPosition === textLength
|
||||||
|
const first = atEnd ? cursorPosition - 2 : cursorPosition - 1
|
||||||
|
const second = atEnd ? cursorPosition - 1 : cursorPosition
|
||||||
|
|
||||||
|
if (text[first] === "\n" || text[second] === "\n") return
|
||||||
|
|
||||||
|
replaceOffsets(first, second + 1, `${text[second]}${text[first]}`)
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alt) {
|
||||||
|
if (event.code === "KeyB") {
|
||||||
|
let pos = cursorPosition
|
||||||
|
while (pos > 0 && /\s/.test(text[pos - 1])) pos -= 1
|
||||||
|
while (pos > 0 && !/\s/.test(text[pos - 1])) pos -= 1
|
||||||
|
setCursorPosition(editorRef, pos)
|
||||||
|
event.preventDefault()
|
||||||
|
queueScroll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === "KeyF") {
|
||||||
|
let pos = cursorPosition
|
||||||
|
while (pos < textLength && /\s/.test(text[pos])) pos += 1
|
||||||
|
while (pos < textLength && !/\s/.test(text[pos])) pos += 1
|
||||||
|
setCursorPosition(editorRef, pos)
|
||||||
|
event.preventDefault()
|
||||||
|
queueScroll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code === "KeyD") {
|
||||||
|
let end = cursorPosition
|
||||||
|
while (end < textLength && /\s/.test(text[end])) end += 1
|
||||||
|
while (end < textLength && !/\s/.test(text[end])) end += 1
|
||||||
|
if (end === cursorPosition) return
|
||||||
|
killText(cursorPosition, end)
|
||||||
|
replaceOffsets(cursorPosition, end, "")
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
||||||
if (event.altKey || event.ctrlKey || event.metaKey) return
|
if (event.altKey || event.ctrlKey || event.metaKey) return
|
||||||
const { collapsed } = getCaretState()
|
const { collapsed } = getCaretState()
|
||||||
@@ -884,11 +1115,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
setStore("imageAttachments", [])
|
setStore("imageAttachments", [])
|
||||||
setStore("mode", "normal")
|
setStore("mode", "normal")
|
||||||
|
|
||||||
const model = {
|
const currentModel = local.model.current()
|
||||||
modelID: local.model.current()!.id,
|
const currentAgent = local.agent.current()
|
||||||
providerID: local.model.current()!.provider.id,
|
if (!currentModel || !currentAgent) {
|
||||||
|
console.warn("No agent or model available for prompt submission")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
const agent = local.agent.current()!.name
|
const model = {
|
||||||
|
modelID: currentModel.id,
|
||||||
|
providerID: currentModel.provider.id,
|
||||||
|
}
|
||||||
|
const agent = currentAgent.name
|
||||||
|
|
||||||
if (isShellMode) {
|
if (isShellMode) {
|
||||||
sdk.client.session
|
sdk.client.session
|
||||||
@@ -1090,6 +1327,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
onInput={handleInput}
|
onInput={handleInput}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
classList={{
|
classList={{
|
||||||
|
"select-text": true,
|
||||||
"w-full px-5 py-3 pr-12 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
|
"w-full px-5 py-3 pr-12 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
|
||||||
"[&_[data-type=file]]:text-icon-info-active": true,
|
"[&_[data-type=file]]:text-icon-info-active": true,
|
||||||
"font-mono!": store.mode === "shell",
|
"font-mono!": store.mode === "shell",
|
||||||
@@ -1128,7 +1366,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
options={local.agent.list().map((agent) => agent.name)}
|
options={local.agent.list().map((agent) => agent.name)}
|
||||||
current={local.agent.current().name}
|
current={local.agent.current()?.name ?? ""}
|
||||||
onSelect={local.agent.set}
|
onSelect={local.agent.set}
|
||||||
class="capitalize"
|
class="capitalize"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { createMemo, Show } from "solid-js"
|
import { createMemo, Show } from "solid-js"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
|
|
||||||
@@ -24,12 +23,11 @@ export function SessionLspIndicator() {
|
|||||||
<Show when={lspStats().total > 0}>
|
<Show when={lspStats().total > 0}>
|
||||||
<Tooltip placement="top" value={tooltipContent()}>
|
<Tooltip placement="top" value={tooltipContent()}>
|
||||||
<div class="flex items-center gap-1 px-2 cursor-default select-none">
|
<div class="flex items-center gap-1 px-2 cursor-default select-none">
|
||||||
<Icon
|
<div
|
||||||
name="code"
|
|
||||||
size="small"
|
|
||||||
classList={{
|
classList={{
|
||||||
"text-icon-critical-base": lspStats().hasError,
|
"size-1.5 rounded-full": true,
|
||||||
"text-icon-success-base": !lspStats().hasError && lspStats().connected > 0,
|
"bg-icon-critical-base": lspStats().hasError,
|
||||||
|
"bg-icon-success-base": !lspStats().hasError && lspStats().connected > 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span class="text-12-regular text-text-weak">{lspStats().connected} LSP</span>
|
<span class="text-12-regular text-text-weak">{lspStats().connected} LSP</span>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { createMemo, Show } from "solid-js"
|
import { createMemo, Show } from "solid-js"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { DialogSelectMcp } from "@/components/dialog-select-mcp"
|
import { DialogSelectMcp } from "@/components/dialog-select-mcp"
|
||||||
@@ -21,12 +20,11 @@ export function SessionMcpIndicator() {
|
|||||||
return (
|
return (
|
||||||
<Show when={mcpStats().total > 0}>
|
<Show when={mcpStats().total > 0}>
|
||||||
<Button variant="ghost" onClick={() => dialog.show(() => <DialogSelectMcp />)}>
|
<Button variant="ghost" onClick={() => dialog.show(() => <DialogSelectMcp />)}>
|
||||||
<Icon
|
<div
|
||||||
name="mcp"
|
|
||||||
size="small"
|
|
||||||
classList={{
|
classList={{
|
||||||
"text-icon-critical-base": mcpStats().failed,
|
"size-1.5 rounded-full": true,
|
||||||
"text-icon-success-base": !mcpStats().failed && mcpStats().enabled > 0,
|
"bg-icon-critical-base": mcpStats().failed,
|
||||||
|
"bg-icon-success-base": !mcpStats().failed && mcpStats().enabled > 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span class="text-12-regular text-text-weak">{mcpStats().enabled} MCP</span>
|
<span class="text-12-regular text-text-weak">{mcpStats().enabled} MCP</span>
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { createMemo, Show, type ParentProps } from "solid-js"
|
import { createMemo, Show, type ParentProps } from "solid-js"
|
||||||
import { usePlatform } from "@/context/platform"
|
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
|
import { useServer } from "@/context/server"
|
||||||
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
|
import { DialogSelectServer } from "@/components/dialog-select-server"
|
||||||
|
|
||||||
export function StatusBar(props: ParentProps) {
|
export function StatusBar(props: ParentProps) {
|
||||||
const platform = usePlatform()
|
const dialog = useDialog()
|
||||||
|
const server = useServer()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
|
|
||||||
@@ -19,9 +23,26 @@ export function StatusBar(props: ParentProps) {
|
|||||||
return (
|
return (
|
||||||
<div class="h-8 w-full shrink-0 flex items-center justify-between px-2 border-t border-border-weak-base bg-background-base">
|
<div class="h-8 w-full shrink-0 flex items-center justify-between px-2 border-t border-border-weak-base bg-background-base">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<Show when={platform.version}>
|
<div class="flex items-center gap-1">
|
||||||
<span class="text-12-regular text-text-weak">v{platform.version}</span>
|
<Button
|
||||||
</Show>
|
size="small"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => {
|
||||||
|
dialog.show(() => <DialogSelectServer />)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
classList={{
|
||||||
|
"size-1.5 rounded-full": true,
|
||||||
|
"bg-icon-success-base": server.healthy() === true,
|
||||||
|
"bg-icon-critical-base": server.healthy() === false,
|
||||||
|
"bg-border-weak-base": server.healthy() === undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span class="text-12-regular text-text-weak">{server.name}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<Show when={directoryDisplay()}>
|
<Show when={directoryDisplay()}>
|
||||||
<span class="text-12-regular text-text-weak">{directoryDisplay()}</span>
|
<span class="text-12-regular text-text-weak">{directoryDisplay()}</span>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
style={{ "background-color": terminalColors().background }}
|
style={{ "background-color": terminalColors().background }}
|
||||||
classList={{
|
classList={{
|
||||||
...(local.classList ?? {}),
|
...(local.classList ?? {}),
|
||||||
|
"select-text": true,
|
||||||
"size-full px-6 py-3 font-mono": true,
|
"size-full px-6 py-3 font-mono": true,
|
||||||
[local.class ?? ""]: !!local.class,
|
[local.class ?? ""]: !!local.class,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,34 +1,41 @@
|
|||||||
import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2/client"
|
import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2/client"
|
||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
||||||
|
import { onCleanup } from "solid-js"
|
||||||
import { usePlatform } from "./platform"
|
import { usePlatform } from "./platform"
|
||||||
|
import { useServer } from "./server"
|
||||||
|
|
||||||
export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleContext({
|
export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleContext({
|
||||||
name: "GlobalSDK",
|
name: "GlobalSDK",
|
||||||
init: (props: { url: string }) => {
|
init: () => {
|
||||||
|
const server = useServer()
|
||||||
|
const abort = new AbortController()
|
||||||
|
|
||||||
const eventSdk = createOpencodeClient({
|
const eventSdk = createOpencodeClient({
|
||||||
baseUrl: props.url,
|
baseUrl: server.url,
|
||||||
// signal: AbortSignal.timeout(1000 * 60 * 10),
|
signal: abort.signal,
|
||||||
})
|
})
|
||||||
const emitter = createGlobalEmitter<{
|
const emitter = createGlobalEmitter<{
|
||||||
[key: string]: Event
|
[key: string]: Event
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
eventSdk.global.event().then(async (events) => {
|
void (async () => {
|
||||||
|
const events = await eventSdk.global.event()
|
||||||
for await (const event of events.stream) {
|
for await (const event of events.stream) {
|
||||||
// console.log("event", event)
|
|
||||||
emitter.emit(event.directory ?? "global", event.payload)
|
emitter.emit(event.directory ?? "global", event.payload)
|
||||||
}
|
}
|
||||||
})
|
})().catch(() => undefined)
|
||||||
|
|
||||||
|
onCleanup(() => abort.abort())
|
||||||
|
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: props.url,
|
baseUrl: server.url,
|
||||||
signal: AbortSignal.timeout(1000 * 60 * 10),
|
signal: AbortSignal.timeout(1000 * 60 * 10),
|
||||||
fetch: platform.fetch,
|
fetch: platform.fetch,
|
||||||
throwOnError: true,
|
throwOnError: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
return { url: props.url, client: sdk, event: emitter }
|
return { url: server.url, client: sdk, event: emitter }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { batch, createMemo, onMount } from "solid-js"
|
|||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { useGlobalSync } from "./global-sync"
|
import { useGlobalSync } from "./global-sync"
|
||||||
import { useGlobalSDK } from "./global-sdk"
|
import { useGlobalSDK } from "./global-sdk"
|
||||||
|
import { useServer } from "./server"
|
||||||
import { Project } from "@opencode-ai/sdk/v2"
|
import { Project } from "@opencode-ai/sdk/v2"
|
||||||
import { persisted } from "@/utils/persist"
|
import { persisted } from "@/utils/persist"
|
||||||
|
|
||||||
@@ -34,10 +35,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
init: () => {
|
init: () => {
|
||||||
const globalSdk = useGlobalSDK()
|
const globalSdk = useGlobalSDK()
|
||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
|
const server = useServer()
|
||||||
const [store, setStore, _, ready] = persisted(
|
const [store, setStore, _, ready] = persisted(
|
||||||
"layout.v3",
|
"layout.v4",
|
||||||
createStore({
|
createStore({
|
||||||
projects: [] as { worktree: string; expanded: boolean }[],
|
|
||||||
sidebar: {
|
sidebar: {
|
||||||
opened: false,
|
opened: false,
|
||||||
width: 280,
|
width: 280,
|
||||||
@@ -86,12 +87,12 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
return project
|
return project
|
||||||
}
|
}
|
||||||
|
|
||||||
const enriched = createMemo(() => store.projects.flatMap(enrich))
|
const enriched = createMemo(() => server.projects.list().flatMap(enrich))
|
||||||
const list = createMemo(() => enriched().flatMap(colorize))
|
const list = createMemo(() => enriched().flatMap(colorize))
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
Promise.all(
|
Promise.all(
|
||||||
store.projects.map((project) => {
|
server.projects.list().map((project) => {
|
||||||
return globalSync.project.loadSessions(project.worktree)
|
return globalSync.project.loadSessions(project.worktree)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -102,32 +103,23 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
projects: {
|
projects: {
|
||||||
list,
|
list,
|
||||||
open(directory: string) {
|
open(directory: string) {
|
||||||
if (store.projects.find((x) => x.worktree === directory)) {
|
if (server.projects.list().find((x) => x.worktree === directory)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
globalSync.project.loadSessions(directory)
|
globalSync.project.loadSessions(directory)
|
||||||
setStore("projects", (x) => [{ worktree: directory, expanded: true }, ...x])
|
server.projects.open(directory)
|
||||||
},
|
},
|
||||||
close(directory: string) {
|
close(directory: string) {
|
||||||
setStore("projects", (x) => x.filter((x) => x.worktree !== directory))
|
server.projects.close(directory)
|
||||||
},
|
},
|
||||||
expand(directory: string) {
|
expand(directory: string) {
|
||||||
const index = store.projects.findIndex((x) => x.worktree === directory)
|
server.projects.expand(directory)
|
||||||
if (index !== -1) setStore("projects", index, "expanded", true)
|
|
||||||
},
|
},
|
||||||
collapse(directory: string) {
|
collapse(directory: string) {
|
||||||
const index = store.projects.findIndex((x) => x.worktree === directory)
|
server.projects.collapse(directory)
|
||||||
if (index !== -1) setStore("projects", index, "expanded", false)
|
|
||||||
},
|
},
|
||||||
move(directory: string, toIndex: number) {
|
move(directory: string, toIndex: number) {
|
||||||
setStore("projects", (projects) => {
|
server.projects.move(directory, toIndex)
|
||||||
const fromIndex = projects.findIndex((x) => x.worktree === directory)
|
|
||||||
if (fromIndex === -1 || fromIndex === toIndex) return projects
|
|
||||||
const result = [...projects]
|
|
||||||
const [item] = result.splice(fromIndex, 1)
|
|
||||||
result.splice(toIndex, 0, item)
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sidebar: {
|
sidebar: {
|
||||||
|
|||||||
@@ -65,23 +65,40 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
const agent = (() => {
|
const agent = (() => {
|
||||||
const list = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent" && !x.hidden))
|
const list = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent" && !x.hidden))
|
||||||
const [store, setStore] = createStore<{
|
const [store, setStore] = createStore<{
|
||||||
current: string
|
current?: string
|
||||||
}>({
|
}>({
|
||||||
current: list()[0].name,
|
current: list()[0]?.name,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
list,
|
list,
|
||||||
current() {
|
current() {
|
||||||
return list().find((x) => x.name === store.current)!
|
const available = list()
|
||||||
|
if (available.length === 0) return undefined
|
||||||
|
return available.find((x) => x.name === store.current) ?? available[0]
|
||||||
},
|
},
|
||||||
set(name: string | undefined) {
|
set(name: string | undefined) {
|
||||||
setStore("current", name ?? list()[0].name)
|
const available = list()
|
||||||
|
if (available.length === 0) {
|
||||||
|
setStore("current", undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (name && available.some((x) => x.name === name)) {
|
||||||
|
setStore("current", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setStore("current", available[0].name)
|
||||||
},
|
},
|
||||||
move(direction: 1 | -1) {
|
move(direction: 1 | -1) {
|
||||||
let next = list().findIndex((x) => x.name === store.current) + direction
|
const available = list()
|
||||||
if (next < 0) next = list().length - 1
|
if (available.length === 0) {
|
||||||
if (next >= list().length) next = 0
|
setStore("current", undefined)
|
||||||
const value = list()[next]
|
return
|
||||||
|
}
|
||||||
|
let next = available.findIndex((x) => x.name === store.current) + direction
|
||||||
|
if (next < 0) next = available.length - 1
|
||||||
|
if (next >= available.length) next = 0
|
||||||
|
const value = available[next]
|
||||||
|
if (!value) return
|
||||||
setStore("current", value.name)
|
setStore("current", value.name)
|
||||||
if (value.model)
|
if (value.model)
|
||||||
model.set({
|
model.set({
|
||||||
@@ -182,11 +199,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
|
|
||||||
const current = createMemo(() => {
|
const current = createMemo(() => {
|
||||||
const a = agent.current()
|
const a = agent.current()
|
||||||
|
if (!a) return undefined
|
||||||
const key = getFirstValidModel(
|
const key = getFirstValidModel(
|
||||||
() => ephemeral.model[a.name],
|
() => ephemeral.model[a.name],
|
||||||
() => a.model,
|
() => a.model,
|
||||||
fallbackModel,
|
fallbackModel,
|
||||||
)!
|
)
|
||||||
|
if (!key) return undefined
|
||||||
return find(key)
|
return find(key)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -232,7 +251,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
cycle,
|
cycle,
|
||||||
set(model: ModelKey | undefined, options?: { recent?: boolean }) {
|
set(model: ModelKey | undefined, options?: { recent?: boolean }) {
|
||||||
batch(() => {
|
batch(() => {
|
||||||
setEphemeral("model", agent.current().name, model ?? fallbackModel())
|
const currentAgent = agent.current()
|
||||||
|
if (currentAgent) setEphemeral("model", currentAgent.name, model ?? fallbackModel())
|
||||||
if (model) updateVisibility(model, "show")
|
if (model) updateVisibility(model, "show")
|
||||||
if (options?.recent && model) {
|
if (options?.recent && model) {
|
||||||
const uniq = uniqueBy([model, ...store.recent], (x) => x.providerID + x.modelID)
|
const uniq = uniqueBy([model, ...store.recent], (x) => x.providerID + x.modelID)
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { createStore } from "solid-js/store"
|
|||||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { useGlobalSDK } from "./global-sdk"
|
import { useGlobalSDK } from "./global-sdk"
|
||||||
import { useGlobalSync } from "./global-sync"
|
import { useGlobalSync } from "./global-sync"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
import { Binary } from "@opencode-ai/util/binary"
|
import { Binary } from "@opencode-ai/util/binary"
|
||||||
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { EventSessionError } from "@opencode-ai/sdk/v2"
|
import { EventSessionError } from "@opencode-ai/sdk/v2"
|
||||||
import { makeAudioPlayer } from "@solid-primitives/audio"
|
import { makeAudioPlayer } from "@solid-primitives/audio"
|
||||||
import idleSound from "@opencode-ai/ui/audio/staplebops-01.aac"
|
import idleSound from "@opencode-ai/ui/audio/staplebops-01.aac"
|
||||||
@@ -43,6 +45,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
|
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
|
const platform = usePlatform()
|
||||||
|
|
||||||
const [store, setStore, _, ready] = persisted(
|
const [store, setStore, _, ready] = persisted(
|
||||||
"notification.v1",
|
"notification.v1",
|
||||||
@@ -64,8 +67,8 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
const sessionID = event.properties.sessionID
|
const sessionID = event.properties.sessionID
|
||||||
const [syncStore] = globalSync.child(directory)
|
const [syncStore] = globalSync.child(directory)
|
||||||
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
|
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
|
||||||
const isChild = match.found && syncStore.session[match.index].parentID
|
const session = match.found ? syncStore.session[match.index] : undefined
|
||||||
if (isChild) break
|
if (session?.parentID) break
|
||||||
try {
|
try {
|
||||||
idlePlayer?.play()
|
idlePlayer?.play()
|
||||||
} catch {}
|
} catch {}
|
||||||
@@ -74,25 +77,29 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
type: "turn-complete",
|
type: "turn-complete",
|
||||||
session: sessionID,
|
session: sessionID,
|
||||||
})
|
})
|
||||||
|
const href = `/${base64Encode(directory)}/session/${sessionID}`
|
||||||
|
void platform.notify("Response ready", session?.title ?? sessionID, href)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "session.error": {
|
case "session.error": {
|
||||||
const sessionID = event.properties.sessionID
|
const sessionID = event.properties.sessionID
|
||||||
if (sessionID) {
|
const [syncStore] = globalSync.child(directory)
|
||||||
const [syncStore] = globalSync.child(directory)
|
const match = sessionID ? Binary.search(syncStore.session, sessionID, (s) => s.id) : undefined
|
||||||
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
|
const session = sessionID && match?.found ? syncStore.session[match.index] : undefined
|
||||||
const isChild = match.found && syncStore.session[match.index].parentID
|
if (session?.parentID) break
|
||||||
if (isChild) break
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
errorPlayer?.play()
|
errorPlayer?.play()
|
||||||
} catch {}
|
} catch {}
|
||||||
|
const error = "error" in event.properties ? event.properties.error : undefined
|
||||||
setStore("list", store.list.length, {
|
setStore("list", store.list.length, {
|
||||||
...base,
|
...base,
|
||||||
type: "error",
|
type: "error",
|
||||||
session: sessionID ?? "global",
|
session: sessionID ?? "global",
|
||||||
error: "error" in event.properties ? event.properties.error : undefined,
|
error,
|
||||||
})
|
})
|
||||||
|
const description = session?.title ?? (typeof error === "string" ? error : "An error occurred")
|
||||||
|
const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
|
||||||
|
void platform.notify("Session error", description, href)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ export type Platform = {
|
|||||||
/** Restart the app */
|
/** Restart the app */
|
||||||
restart(): Promise<void>
|
restart(): Promise<void>
|
||||||
|
|
||||||
/** Open native directory picker dialog (Tauri only) */
|
/** Send a system notification (optional deep link) */
|
||||||
|
notify(title: string, description?: string, href?: string): Promise<void>
|
||||||
|
|
||||||
|
/** Open directory picker dialog (native on Tauri, server-backed on web) */
|
||||||
openDirectoryPickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise<string | string[] | null>
|
openDirectoryPickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise<string | string[] | null>
|
||||||
|
|
||||||
/** Open native file picker dialog (Tauri only) */
|
/** Open native file picker dialog (Tauri only) */
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||||
|
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
|
import { batch, createEffect, createMemo, createResource, createSignal, onCleanup } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
|
import { persisted } from "@/utils/persist"
|
||||||
|
|
||||||
|
type StoredProject = { worktree: string; expanded: boolean }
|
||||||
|
|
||||||
|
export function normalizeServerUrl(input: string) {
|
||||||
|
const trimmed = input.trim()
|
||||||
|
if (!trimmed) return
|
||||||
|
const withProtocol = /^https?:\/\//.test(trimmed) ? trimmed : `http://${trimmed}`
|
||||||
|
const cleaned = withProtocol.replace(/\/+$/, "")
|
||||||
|
return cleaned.replace(/^(https?:\/\/[^/]+).*/, "$1")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serverDisplayName(url: string) {
|
||||||
|
if (!url) return ""
|
||||||
|
return url
|
||||||
|
.replace(/^https?:\/\//, "")
|
||||||
|
.replace(/\/+$/, "")
|
||||||
|
.split("/")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function projectsKey(url: string) {
|
||||||
|
if (!url) return ""
|
||||||
|
const host = url.replace(/^https?:\/\//, "").split(":")[0]
|
||||||
|
if (host === "localhost" || host === "127.0.0.1") return "local"
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
export const { use: useServer, provider: ServerProvider } = createSimpleContext({
|
||||||
|
name: "Server",
|
||||||
|
init: (props: { defaultUrl: string }) => {
|
||||||
|
const platform = usePlatform()
|
||||||
|
|
||||||
|
const [store, setStore, _, ready] = persisted(
|
||||||
|
"server.v3",
|
||||||
|
createStore({
|
||||||
|
list: [] as string[],
|
||||||
|
projects: {} as Record<string, StoredProject[]>,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const [active, setActiveRaw] = createSignal("")
|
||||||
|
|
||||||
|
function setActive(input: string) {
|
||||||
|
const url = normalizeServerUrl(input)
|
||||||
|
if (!url) return
|
||||||
|
setActiveRaw(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
function add(input: string) {
|
||||||
|
const url = normalizeServerUrl(input)
|
||||||
|
if (!url) return
|
||||||
|
|
||||||
|
const fallback = normalizeServerUrl(props.defaultUrl)
|
||||||
|
if (fallback && url === fallback) {
|
||||||
|
setActiveRaw(url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
batch(() => {
|
||||||
|
if (!store.list.includes(url)) {
|
||||||
|
setStore("list", store.list.length, url)
|
||||||
|
}
|
||||||
|
setActiveRaw(url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(input: string) {
|
||||||
|
const url = normalizeServerUrl(input)
|
||||||
|
if (!url) return
|
||||||
|
|
||||||
|
const list = store.list.filter((x) => x !== url)
|
||||||
|
const next = active() === url ? (list[0] ?? normalizeServerUrl(props.defaultUrl) ?? "") : active()
|
||||||
|
|
||||||
|
batch(() => {
|
||||||
|
setStore("list", list)
|
||||||
|
setActiveRaw(next)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!ready()) return
|
||||||
|
if (active()) return
|
||||||
|
const url = normalizeServerUrl(props.defaultUrl)
|
||||||
|
if (!url) return
|
||||||
|
setActiveRaw(url)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isReady = createMemo(() => ready() && !!active())
|
||||||
|
|
||||||
|
const [healthy, { refetch }] = createResource(
|
||||||
|
() => active() || undefined,
|
||||||
|
async (url) => {
|
||||||
|
if (!url) return
|
||||||
|
|
||||||
|
const sdk = createOpencodeClient({
|
||||||
|
baseUrl: url,
|
||||||
|
fetch: platform.fetch,
|
||||||
|
signal: AbortSignal.timeout(2000),
|
||||||
|
})
|
||||||
|
return sdk.global
|
||||||
|
.health()
|
||||||
|
.then((x) => x.data?.healthy === true)
|
||||||
|
.catch(() => false)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!active()) return
|
||||||
|
const interval = setInterval(() => refetch(), 10_000)
|
||||||
|
onCleanup(() => clearInterval(interval))
|
||||||
|
})
|
||||||
|
|
||||||
|
const origin = createMemo(() => projectsKey(active()))
|
||||||
|
const projectsList = createMemo(() => store.projects[origin()] ?? [])
|
||||||
|
|
||||||
|
return {
|
||||||
|
ready: isReady,
|
||||||
|
healthy,
|
||||||
|
get url() {
|
||||||
|
return active()
|
||||||
|
},
|
||||||
|
get name() {
|
||||||
|
return serverDisplayName(active())
|
||||||
|
},
|
||||||
|
get list() {
|
||||||
|
return store.list
|
||||||
|
},
|
||||||
|
setActive,
|
||||||
|
add,
|
||||||
|
remove,
|
||||||
|
projects: {
|
||||||
|
list: projectsList,
|
||||||
|
open(directory: string) {
|
||||||
|
const key = origin()
|
||||||
|
if (!key) return
|
||||||
|
const current = store.projects[key] ?? []
|
||||||
|
if (current.find((x) => x.worktree === directory)) return
|
||||||
|
setStore("projects", key, [{ worktree: directory, expanded: true }, ...current])
|
||||||
|
},
|
||||||
|
close(directory: string) {
|
||||||
|
const key = origin()
|
||||||
|
if (!key) return
|
||||||
|
const current = store.projects[key] ?? []
|
||||||
|
setStore(
|
||||||
|
"projects",
|
||||||
|
key,
|
||||||
|
current.filter((x) => x.worktree !== directory),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
expand(directory: string) {
|
||||||
|
const key = origin()
|
||||||
|
if (!key) return
|
||||||
|
const current = store.projects[key] ?? []
|
||||||
|
const index = current.findIndex((x) => x.worktree === directory)
|
||||||
|
if (index !== -1) setStore("projects", key, index, "expanded", true)
|
||||||
|
},
|
||||||
|
collapse(directory: string) {
|
||||||
|
const key = origin()
|
||||||
|
if (!key) return
|
||||||
|
const current = store.projects[key] ?? []
|
||||||
|
const index = current.findIndex((x) => x.worktree === directory)
|
||||||
|
if (index !== -1) setStore("projects", key, index, "expanded", false)
|
||||||
|
},
|
||||||
|
move(directory: string, toIndex: number) {
|
||||||
|
const key = origin()
|
||||||
|
if (!key) return
|
||||||
|
const current = store.projects[key] ?? []
|
||||||
|
const fromIndex = current.findIndex((x) => x.worktree === directory)
|
||||||
|
if (fromIndex === -1 || fromIndex === toIndex) return
|
||||||
|
const result = [...current]
|
||||||
|
const [item] = result.splice(fromIndex, 1)
|
||||||
|
result.splice(toIndex, 0, item)
|
||||||
|
setStore("projects", key, result)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -20,6 +20,36 @@ const platform: Platform = {
|
|||||||
restart: async () => {
|
restart: async () => {
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
},
|
},
|
||||||
|
notify: async (title, description, href) => {
|
||||||
|
if (!("Notification" in window)) return
|
||||||
|
|
||||||
|
const permission =
|
||||||
|
Notification.permission === "default"
|
||||||
|
? await Notification.requestPermission().catch(() => "denied")
|
||||||
|
: Notification.permission
|
||||||
|
|
||||||
|
if (permission !== "granted") return
|
||||||
|
|
||||||
|
const inView = document.visibilityState === "visible" && document.hasFocus()
|
||||||
|
if (inView) return
|
||||||
|
|
||||||
|
await Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
const notification = new Notification(title, {
|
||||||
|
body: description ?? "",
|
||||||
|
icon: "https://opencode.ai/favicon-96x96.png",
|
||||||
|
})
|
||||||
|
notification.onclick = () => {
|
||||||
|
window.focus()
|
||||||
|
if (href) {
|
||||||
|
window.history.pushState(null, "", href)
|
||||||
|
window.dispatchEvent(new PopStateEvent("popstate"))
|
||||||
|
}
|
||||||
|
notification.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => undefined)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
render(
|
render(
|
||||||
|
|||||||
@@ -20,11 +20,51 @@ function isInitError(error: unknown): error is InitError {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function safeJson(value: unknown): string {
|
||||||
|
const seen = new WeakSet<object>()
|
||||||
|
const json = JSON.stringify(
|
||||||
|
value,
|
||||||
|
(_key, val) => {
|
||||||
|
if (typeof val === "bigint") return val.toString()
|
||||||
|
if (typeof val === "object" && val) {
|
||||||
|
if (seen.has(val)) return "[Circular]"
|
||||||
|
seen.add(val)
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
return json ?? String(value)
|
||||||
|
}
|
||||||
|
|
||||||
function formatInitError(error: InitError): string {
|
function formatInitError(error: InitError): string {
|
||||||
const data = error.data
|
const data = error.data
|
||||||
switch (error.name) {
|
switch (error.name) {
|
||||||
case "MCPFailed":
|
case "MCPFailed":
|
||||||
return `MCP server "${data.name}" failed. Note, opencode does not support MCP authentication yet.`
|
return `MCP server "${data.name}" failed. Note, opencode does not support MCP authentication yet.`
|
||||||
|
case "ProviderAuthError": {
|
||||||
|
const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
|
||||||
|
const message = typeof data.message === "string" ? data.message : safeJson(data.message)
|
||||||
|
return `Provider authentication failed (${providerID}): ${message}`
|
||||||
|
}
|
||||||
|
case "APIError": {
|
||||||
|
const message = typeof data.message === "string" ? data.message : "API error"
|
||||||
|
const lines: string[] = [message]
|
||||||
|
|
||||||
|
if (typeof data.statusCode === "number") {
|
||||||
|
lines.push(`Status: ${data.statusCode}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.isRetryable === "boolean") {
|
||||||
|
lines.push(`Retryable: ${data.isRetryable}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data.responseBody === "string" && data.responseBody) {
|
||||||
|
lines.push(`Response body:\n${data.responseBody}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n")
|
||||||
|
}
|
||||||
case "ProviderModelNotFoundError": {
|
case "ProviderModelNotFoundError": {
|
||||||
const { providerID, modelID, suggestions } = data as {
|
const { providerID, modelID, suggestions } = data as {
|
||||||
providerID: string
|
providerID: string
|
||||||
@@ -37,10 +77,14 @@ function formatInitError(error: InitError): string {
|
|||||||
`Check your config (opencode.json) provider/model names`,
|
`Check your config (opencode.json) provider/model names`,
|
||||||
].join("\n")
|
].join("\n")
|
||||||
}
|
}
|
||||||
case "ProviderInitError":
|
case "ProviderInitError": {
|
||||||
return `Failed to initialize provider "${data.providerID}". Check credentials and configuration.`
|
const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
|
||||||
case "ConfigJsonError":
|
return `Failed to initialize provider "${providerID}". Check credentials and configuration.`
|
||||||
return `Config file at ${data.path} is not valid JSON(C)` + (data.message ? `: ${data.message}` : "")
|
}
|
||||||
|
case "ConfigJsonError": {
|
||||||
|
const message = typeof data.message === "string" ? data.message : ""
|
||||||
|
return `Config file at ${data.path} is not valid JSON(C)` + (message ? `: ${message}` : "")
|
||||||
|
}
|
||||||
case "ConfigDirectoryTypoError":
|
case "ConfigDirectoryTypoError":
|
||||||
return `Directory "${data.dir}" in ${data.path} is not valid. Rename the directory to "${data.suggestion}" or remove it. This is a common typo.`
|
return `Directory "${data.dir}" in ${data.path} is not valid. Rename the directory to "${data.suggestion}" or remove it. This is a common typo.`
|
||||||
case "ConfigFrontmatterError":
|
case "ConfigFrontmatterError":
|
||||||
@@ -51,14 +95,14 @@ function formatInitError(error: InitError): string {
|
|||||||
(issue: { message: string; path: string[] }) => "↳ " + issue.message + " " + issue.path.join("."),
|
(issue: { message: string; path: string[] }) => "↳ " + issue.message + " " + issue.path.join("."),
|
||||||
)
|
)
|
||||||
: []
|
: []
|
||||||
return [`Config file at ${data.path} is invalid` + (data.message ? `: ${data.message}` : ""), ...issues].join(
|
const message = typeof data.message === "string" ? data.message : ""
|
||||||
"\n",
|
return [`Config file at ${data.path} is invalid` + (message ? `: ${message}` : ""), ...issues].join("\n")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case "UnknownError":
|
case "UnknownError":
|
||||||
return String(data.message)
|
return typeof data.message === "string" ? data.message : safeJson(data)
|
||||||
default:
|
default:
|
||||||
return data.message ? String(data.message) : JSON.stringify(data, null, 2)
|
if (typeof data.message === "string") return data.message
|
||||||
|
return safeJson(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +113,7 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
|
|||||||
const message = formatInitError(error)
|
const message = formatInitError(error)
|
||||||
if (depth > 0 && parentMessage === message) return ""
|
if (depth > 0 && parentMessage === message) return ""
|
||||||
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
|
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
|
||||||
return indent + message
|
return indent + `${error.name}\n${message}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@@ -77,15 +121,34 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
|
|||||||
const parts: string[] = []
|
const parts: string[] = []
|
||||||
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
|
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
|
||||||
|
|
||||||
if (!isDuplicate) {
|
const header = `${error.name}${error.message ? `: ${error.message}` : ""}`
|
||||||
// Stack already includes error name and message, so prefer it
|
const stack = error.stack?.trim()
|
||||||
parts.push(indent + (error.stack ?? `${error.name}: ${error.message}`))
|
|
||||||
} else if (error.stack) {
|
if (stack) {
|
||||||
// Duplicate message - only show the stack trace lines (skip message)
|
const startsWithHeader = stack.startsWith(header)
|
||||||
const trace = error.stack.split("\n").slice(1).join("\n").trim()
|
|
||||||
if (trace) {
|
if (isDuplicate && startsWithHeader) {
|
||||||
parts.push(trace)
|
const trace = stack.split("\n").slice(1).join("\n").trim()
|
||||||
|
if (trace) {
|
||||||
|
parts.push(indent + trace)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isDuplicate && !startsWithHeader) {
|
||||||
|
parts.push(indent + stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDuplicate && startsWithHeader) {
|
||||||
|
parts.push(indent + stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDuplicate && !startsWithHeader) {
|
||||||
|
parts.push(indent + `${header}\n${stack}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stack && !isDuplicate) {
|
||||||
|
parts.push(indent + header)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.cause) {
|
if (error.cause) {
|
||||||
@@ -105,7 +168,7 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
|
|||||||
}
|
}
|
||||||
|
|
||||||
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
|
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
|
||||||
return indent + JSON.stringify(error, null, 2)
|
return indent + safeJson(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatError(error: unknown): string {
|
function formatError(error: unknown): string {
|
||||||
@@ -131,7 +194,7 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
|||||||
readOnly
|
readOnly
|
||||||
copyable
|
copyable
|
||||||
multiline
|
multiline
|
||||||
class="max-h-96 w-full font-mono text-xs no-scrollbar whitespace-pre"
|
class="max-h-96 w-full font-mono text-xs no-scrollbar"
|
||||||
label="Error Details"
|
label="Error Details"
|
||||||
hideLabel
|
hideLabel
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,12 +8,18 @@ import { base64Encode } from "@opencode-ai/util/encode"
|
|||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { DateTime } from "luxon"
|
import { DateTime } from "luxon"
|
||||||
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
||||||
|
import { DialogSelectServer } from "@/components/dialog-select-server"
|
||||||
|
import { useServer } from "@/context/server"
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const sync = useGlobalSync()
|
const sync = useGlobalSync()
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
|
const dialog = useDialog()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const server = useServer()
|
||||||
const homedir = createMemo(() => sync.data.path.home)
|
const homedir = createMemo(() => sync.data.path.home)
|
||||||
|
|
||||||
function openProject(directory: string) {
|
function openProject(directory: string) {
|
||||||
@@ -22,32 +28,57 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function chooseProject() {
|
async function chooseProject() {
|
||||||
const result = await platform.openDirectoryPickerDialog?.({
|
function resolve(result: string | string[] | null) {
|
||||||
title: "Open project",
|
if (Array.isArray(result)) {
|
||||||
multiple: true,
|
for (const directory of result) {
|
||||||
})
|
openProject(directory)
|
||||||
if (Array.isArray(result)) {
|
}
|
||||||
for (const directory of result) {
|
} else if (result) {
|
||||||
openProject(directory)
|
openProject(result)
|
||||||
}
|
}
|
||||||
} else if (result) {
|
}
|
||||||
openProject(result)
|
|
||||||
|
if (platform.openDirectoryPickerDialog) {
|
||||||
|
const result = await platform.openDirectoryPickerDialog?.({
|
||||||
|
title: "Open project",
|
||||||
|
multiple: true,
|
||||||
|
})
|
||||||
|
resolve(result)
|
||||||
|
} else {
|
||||||
|
dialog.show(
|
||||||
|
() => <DialogSelectDirectory multiple={true} onSelect={resolve} />,
|
||||||
|
() => resolve(null),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="mx-auto mt-55">
|
<div class="mx-auto mt-55">
|
||||||
<Logo class="w-xl opacity-12" />
|
<Logo class="w-xl opacity-12" />
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
variant="ghost"
|
||||||
|
class="mt-4 mx-auto text-14-regular text-text-weak"
|
||||||
|
onClick={() => dialog.show(() => <DialogSelectServer />)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
classList={{
|
||||||
|
"size-2 rounded-full": true,
|
||||||
|
"bg-icon-success-base": server.healthy() === true,
|
||||||
|
"bg-icon-critical-base": server.healthy() === false,
|
||||||
|
"bg-border-weak-base": server.healthy() === undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{server.name}
|
||||||
|
</Button>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={sync.data.project.length > 0}>
|
<Match when={sync.data.project.length > 0}>
|
||||||
<div class="mt-20 w-full flex flex-col gap-4">
|
<div class="mt-20 w-full flex flex-col gap-4">
|
||||||
<div class="flex gap-2 items-center justify-between pl-3">
|
<div class="flex gap-2 items-center justify-between pl-3">
|
||||||
<div class="text-14-medium text-text-strong">Recent projects</div>
|
<div class="text-14-medium text-text-strong">Recent projects</div>
|
||||||
<Show when={platform.openDirectoryPickerDialog}>
|
<Button icon="folder-add-left" size="normal" class="pl-2 pr-3" onClick={chooseProject}>
|
||||||
<Button icon="folder-add-left" size="normal" class="pl-2 pr-3" onClick={chooseProject}>
|
Open project
|
||||||
Open project
|
</Button>
|
||||||
</Button>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
<ul class="flex flex-col gap-2">
|
<ul class="flex flex-col gap-2">
|
||||||
<For
|
<For
|
||||||
@@ -80,11 +111,9 @@ export default function Home() {
|
|||||||
<div class="text-12-regular text-text-weak">Get started by opening a local project</div>
|
<div class="text-12-regular text-text-weak">Get started by opening a local project</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<Show when={platform.openDirectoryPickerDialog}>
|
<Button class="px-3" onClick={chooseProject}>
|
||||||
<Button class="px-3" onClick={chooseProject}>
|
Open project
|
||||||
Open project
|
</Button>
|
||||||
</Button>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
@@ -50,8 +50,10 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||||||
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
||||||
import { DialogSelectProvider } from "@/components/dialog-select-provider"
|
import { DialogSelectProvider } from "@/components/dialog-select-provider"
|
||||||
import { DialogEditProject } from "@/components/dialog-edit-project"
|
import { DialogEditProject } from "@/components/dialog-edit-project"
|
||||||
|
import { DialogSelectServer } from "@/components/dialog-select-server"
|
||||||
import { useCommand, type CommandOption } from "@/context/command"
|
import { useCommand, type CommandOption } from "@/context/command"
|
||||||
import { ConstrainDragXAxis } from "@/utils/solid-dnd"
|
import { ConstrainDragXAxis } from "@/utils/solid-dnd"
|
||||||
|
import { DialogSelectDirectory } from "@/components/dialog-select-directory"
|
||||||
|
|
||||||
export default function Layout(props: ParentProps) {
|
export default function Layout(props: ParentProps) {
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
@@ -161,27 +163,33 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (e.details?.type !== "permission.updated") return
|
if (e.details?.type !== "permission.updated") return
|
||||||
const directory = e.name
|
const directory = e.name
|
||||||
const permission = e.details.properties
|
const permission = e.details.properties
|
||||||
|
const currentDir = params.dir ? base64Decode(params.dir) : undefined
|
||||||
|
const currentSession = params.id
|
||||||
|
const [store] = globalSync.child(directory)
|
||||||
|
const session = store.session.find((s) => s.id === permission.sessionID)
|
||||||
|
const sessionTitle = session?.title ?? "New session"
|
||||||
|
const projectName = getFilename(directory)
|
||||||
|
const description = `${sessionTitle} in ${projectName} needs permission`
|
||||||
|
const href = `/${base64Encode(directory)}/session/${permission.sessionID}`
|
||||||
|
void platform.notify("Permission required", description, href)
|
||||||
|
|
||||||
|
if (directory === currentDir && permission.sessionID === currentSession) return
|
||||||
|
if (directory === currentDir && session?.parentID === currentSession) return
|
||||||
|
|
||||||
const sessionKey = `${directory}:${permission.sessionID}`
|
const sessionKey = `${directory}:${permission.sessionID}`
|
||||||
if (seenSessions.has(sessionKey)) return
|
if (seenSessions.has(sessionKey)) return
|
||||||
seenSessions.add(sessionKey)
|
seenSessions.add(sessionKey)
|
||||||
const currentDir = params.dir ? base64Decode(params.dir) : undefined
|
|
||||||
const currentSession = params.id
|
|
||||||
if (directory === currentDir && permission.sessionID === currentSession) return
|
|
||||||
const [store] = globalSync.child(directory)
|
|
||||||
const session = store.session.find((s) => s.id === permission.sessionID)
|
|
||||||
if (directory === currentDir && session?.parentID === currentSession) return
|
|
||||||
const sessionTitle = session?.title ?? "New session"
|
|
||||||
const projectName = getFilename(directory)
|
|
||||||
const toastId = showToast({
|
const toastId = showToast({
|
||||||
persistent: true,
|
persistent: true,
|
||||||
icon: "checklist",
|
icon: "checklist",
|
||||||
title: "Permission required",
|
title: "Permission required",
|
||||||
description: `${sessionTitle} in ${projectName} needs permission`,
|
description,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
label: "Go to session",
|
label: "Go to session",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
navigate(`/${base64Encode(directory)}/session/${permission.sessionID}`)
|
navigate(href)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -332,23 +340,25 @@ export default function Layout(props: ParentProps) {
|
|||||||
keybind: "mod+b",
|
keybind: "mod+b",
|
||||||
onSelect: () => layout.sidebar.toggle(),
|
onSelect: () => layout.sidebar.toggle(),
|
||||||
},
|
},
|
||||||
...(platform.openDirectoryPickerDialog
|
{
|
||||||
? [
|
id: "project.open",
|
||||||
{
|
title: "Open project",
|
||||||
id: "project.open",
|
category: "Project",
|
||||||
title: "Open project",
|
keybind: "mod+o",
|
||||||
category: "Project",
|
onSelect: () => chooseProject(),
|
||||||
keybind: "mod+o",
|
},
|
||||||
onSelect: () => chooseProject(),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
{
|
||||||
id: "provider.connect",
|
id: "provider.connect",
|
||||||
title: "Connect provider",
|
title: "Connect provider",
|
||||||
category: "Provider",
|
category: "Provider",
|
||||||
onSelect: () => connectProvider(),
|
onSelect: () => connectProvider(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "server.switch",
|
||||||
|
title: "Switch server",
|
||||||
|
category: "Server",
|
||||||
|
onSelect: () => openServer(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "session.previous",
|
id: "session.previous",
|
||||||
title: "Previous session",
|
title: "Previous session",
|
||||||
@@ -424,6 +434,10 @@ export default function Layout(props: ParentProps) {
|
|||||||
dialog.show(() => <DialogSelectProvider />)
|
dialog.show(() => <DialogSelectProvider />)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openServer() {
|
||||||
|
dialog.show(() => <DialogSelectServer />)
|
||||||
|
}
|
||||||
|
|
||||||
function navigateToProject(directory: string | undefined) {
|
function navigateToProject(directory: string | undefined) {
|
||||||
if (!directory) return
|
if (!directory) return
|
||||||
const lastSession = store.lastSession[directory]
|
const lastSession = store.lastSession[directory]
|
||||||
@@ -451,17 +465,28 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function chooseProject() {
|
async function chooseProject() {
|
||||||
const result = await platform.openDirectoryPickerDialog?.({
|
function resolve(result: string | string[] | null) {
|
||||||
title: "Open project",
|
if (Array.isArray(result)) {
|
||||||
multiple: true,
|
for (const directory of result) {
|
||||||
})
|
openProject(directory, false)
|
||||||
if (Array.isArray(result)) {
|
}
|
||||||
for (const directory of result) {
|
navigateToProject(result[0])
|
||||||
openProject(directory, false)
|
} else if (result) {
|
||||||
|
openProject(result)
|
||||||
}
|
}
|
||||||
navigateToProject(result[0])
|
}
|
||||||
} else if (result) {
|
|
||||||
openProject(result)
|
if (platform.openDirectoryPickerDialog) {
|
||||||
|
const result = await platform.openDirectoryPickerDialog?.({
|
||||||
|
title: "Open project",
|
||||||
|
multiple: true,
|
||||||
|
})
|
||||||
|
resolve(result)
|
||||||
|
} else {
|
||||||
|
dialog.show(
|
||||||
|
() => <DialogSelectDirectory multiple={true} onSelect={resolve} />,
|
||||||
|
() => resolve(null),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -916,7 +941,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">
|
<div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={!providers.paid().length && expanded()}>
|
<Match when={providers.all().length > 0 && !providers.paid().length && expanded()}>
|
||||||
<div class="rounded-md bg-background-stronger shadow-xs-border-base">
|
<div class="rounded-md bg-background-stronger shadow-xs-border-base">
|
||||||
<div class="p-3 flex flex-col gap-2">
|
<div class="p-3 flex flex-col gap-2">
|
||||||
<div class="text-12-medium text-text-strong">Getting started</div>
|
<div class="text-12-medium text-text-strong">Getting started</div>
|
||||||
@@ -935,7 +960,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={providers.all().length > 0}>
|
||||||
<Tooltip placement="right" value="Connect provider" inactive={expanded()}>
|
<Tooltip placement="right" value="Connect provider" inactive={expanded()}>
|
||||||
<Button
|
<Button
|
||||||
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
|
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
|
||||||
@@ -949,30 +974,28 @@ export default function Layout(props: ParentProps) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
<Show when={platform.openDirectoryPickerDialog}>
|
<Tooltip
|
||||||
<Tooltip
|
placement="right"
|
||||||
placement="right"
|
value={
|
||||||
value={
|
<div class="flex items-center gap-2">
|
||||||
<div class="flex items-center gap-2">
|
<span>Open project</span>
|
||||||
<span>Open project</span>
|
<Show when={!sidebarProps.mobile}>
|
||||||
<Show when={!sidebarProps.mobile}>
|
<span class="text-icon-base text-12-medium">{command.keybind("project.open")}</span>
|
||||||
<span class="text-icon-base text-12-medium">{command.keybind("project.open")}</span>
|
</Show>
|
||||||
</Show>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
inactive={expanded()}
|
||||||
inactive={expanded()}
|
>
|
||||||
|
<Button
|
||||||
|
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
|
||||||
|
variant="ghost"
|
||||||
|
size="large"
|
||||||
|
icon="folder-add-left"
|
||||||
|
onClick={chooseProject}
|
||||||
>
|
>
|
||||||
<Button
|
<Show when={expanded()}>Open project</Show>
|
||||||
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
|
</Button>
|
||||||
variant="ghost"
|
</Tooltip>
|
||||||
size="large"
|
|
||||||
icon="folder-add-left"
|
|
||||||
onClick={chooseProject}
|
|
||||||
>
|
|
||||||
<Show when={expanded()}>Open project</Show>
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</Show>
|
|
||||||
<Tooltip placement="right" value="Share feedback" inactive={expanded()}>
|
<Tooltip placement="right" value="Share feedback" inactive={expanded()}>
|
||||||
<Button
|
<Button
|
||||||
as={"a"}
|
as={"a"}
|
||||||
@@ -992,7 +1015,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative flex-1 min-h-0 flex flex-col">
|
<div class="relative flex-1 min-h-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text">
|
||||||
<Header
|
<Header
|
||||||
navigateToProject={navigateToProject}
|
navigateToProject={navigateToProject}
|
||||||
navigateToSession={navigateToSession}
|
navigateToSession={navigateToSession}
|
||||||
|
|||||||
@@ -858,7 +858,7 @@ export default function Page() {
|
|||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</div>
|
</div>
|
||||||
<Show when={diffs().length}>
|
<Show when={diffs().length}>
|
||||||
<Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden contain-strict">
|
<Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict">
|
||||||
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
|
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
|
||||||
<SessionReview
|
<SessionReview
|
||||||
classes={{
|
classes={{
|
||||||
@@ -884,7 +884,7 @@ export default function Page() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<Tabs.Content value={tab} class="select-text mt-3">
|
<Tabs.Content value={tab} class="mt-3">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={file()}>
|
<Match when={file()}>
|
||||||
{(f) => (
|
{(f) => (
|
||||||
@@ -896,7 +896,7 @@ export default function Page() {
|
|||||||
cacheKey: checksum(f().content?.content ?? ""),
|
cacheKey: checksum(f().content?.content ?? ""),
|
||||||
}}
|
}}
|
||||||
overflow="scroll"
|
overflow="scroll"
|
||||||
class="pb-40"
|
class="select-text pb-40"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Match>
|
</Match>
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
target: "esnext",
|
target: "esnext",
|
||||||
sourcemap: true,
|
// sourcemap: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo -b",
|
"typecheck": "tsgo -b",
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
"@tauri-apps/plugin-dialog": "~2",
|
"@tauri-apps/plugin-dialog": "~2",
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"@tauri-apps/plugin-os": "~2",
|
"@tauri-apps/plugin-os": "~2",
|
||||||
|
"@tauri-apps/plugin-notification": "~2",
|
||||||
"@tauri-apps/plugin-process": "~2",
|
"@tauri-apps/plugin-process": "~2",
|
||||||
"@tauri-apps/plugin-shell": "~2",
|
"@tauri-apps/plugin-shell": "~2",
|
||||||
"@tauri-apps/plugin-store": "~2",
|
"@tauri-apps/plugin-store": "~2",
|
||||||
|
|||||||
Generated
+58
@@ -2210,6 +2210,18 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mac-notification-sys"
|
||||||
|
version = "0.6.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65fd3f75411f4725061682ed91f131946e912859d0044d39c4ec0aac818d7621"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"objc2 0.6.3",
|
||||||
|
"objc2-foundation 0.3.2",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markup5ever"
|
name = "markup5ever"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
@@ -2384,6 +2396,20 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify-rust"
|
||||||
|
version = "4.11.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6442248665a5aa2514e794af3b39661a8e73033b1cc5e59899e1276117ee4400"
|
||||||
|
dependencies = [
|
||||||
|
"futures-lite",
|
||||||
|
"log",
|
||||||
|
"mac-notification-sys",
|
||||||
|
"serde",
|
||||||
|
"tauri-winrt-notification",
|
||||||
|
"zbus",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2758,6 +2784,7 @@ dependencies = [
|
|||||||
"tauri-plugin-clipboard-manager",
|
"tauri-plugin-clipboard-manager",
|
||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-http",
|
"tauri-plugin-http",
|
||||||
|
"tauri-plugin-notification",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"tauri-plugin-os",
|
"tauri-plugin-os",
|
||||||
"tauri-plugin-process",
|
"tauri-plugin-process",
|
||||||
@@ -4519,6 +4546,25 @@ dependencies = [
|
|||||||
"urlpattern",
|
"urlpattern",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-notification"
|
||||||
|
version = "2.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"notify-rust",
|
||||||
|
"rand 0.9.2",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_repr",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"time",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-opener"
|
name = "tauri-plugin-opener"
|
||||||
version = "2.5.2"
|
version = "2.5.2"
|
||||||
@@ -4754,6 +4800,18 @@ dependencies = [
|
|||||||
"toml 0.9.8",
|
"toml 0.9.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-winrt-notification"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
|
||||||
|
dependencies = [
|
||||||
|
"quick-xml 0.37.5",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"windows",
|
||||||
|
"windows-version",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.23.0"
|
version = "3.23.0"
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ tauri-plugin-store = "2"
|
|||||||
tauri-plugin-window-state = "2"
|
tauri-plugin-window-state = "2"
|
||||||
tauri-plugin-clipboard-manager = "2"
|
tauri-plugin-clipboard-manager = "2"
|
||||||
tauri-plugin-http = "2"
|
tauri-plugin-http = "2"
|
||||||
|
tauri-plugin-notification = "2"
|
||||||
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
"opener:default",
|
"opener:default",
|
||||||
"core:window:allow-start-dragging",
|
"core:window:allow-start-dragging",
|
||||||
"core:webview:allow-set-webview-zoom",
|
"core:webview:allow-set-webview-zoom",
|
||||||
|
"core:window:allow-is-focused",
|
||||||
|
"core:window:allow-show",
|
||||||
|
"core:window:allow-unminimize",
|
||||||
|
"core:window:allow-set-focus",
|
||||||
"shell:default",
|
"shell:default",
|
||||||
"updater:default",
|
"updater:default",
|
||||||
"dialog:default",
|
"dialog:default",
|
||||||
@@ -15,6 +19,7 @@
|
|||||||
"store:default",
|
"store:default",
|
||||||
"window-state:default",
|
"window-state:default",
|
||||||
"os:default",
|
"os:default",
|
||||||
|
"notification:default",
|
||||||
{
|
{
|
||||||
"identifier": "http:default",
|
"identifier": "http:default",
|
||||||
"allow": [{ "url": "http://*" }, { "url": "https://*" }, { "url": "http://*:*/*" }]
|
"allow": [{ "url": "http://*" }, { "url": "https://*" }, { "url": "http://*:*/*" }]
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
.plugin(tauri_plugin_http::init())
|
.plugin(tauri_plugin_http::init())
|
||||||
|
.plugin(tauri_plugin_notification::init())
|
||||||
.plugin(PinchZoomDisablePlugin)
|
.plugin(PinchZoomDisablePlugin)
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
kill_sidecar,
|
kill_sidecar,
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import { UPDATER_ENABLED } from "./updater"
|
|||||||
import { createMenu } from "./menu"
|
import { createMenu } from "./menu"
|
||||||
import { check, Update } from "@tauri-apps/plugin-updater"
|
import { check, Update } from "@tauri-apps/plugin-updater"
|
||||||
import { invoke } from "@tauri-apps/api/core"
|
import { invoke } from "@tauri-apps/api/core"
|
||||||
|
import { getCurrentWindow } from "@tauri-apps/api/window"
|
||||||
|
import { isPermissionGranted, requestPermission } from "@tauri-apps/plugin-notification"
|
||||||
import { relaunch } from "@tauri-apps/plugin-process"
|
import { relaunch } from "@tauri-apps/plugin-process"
|
||||||
import pkg from "../package.json"
|
import pkg from "../package.json"
|
||||||
|
|
||||||
@@ -94,6 +96,33 @@ const platform: Platform = {
|
|||||||
await relaunch()
|
await relaunch()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
notify: async (title, description, href) => {
|
||||||
|
const granted = await isPermissionGranted().catch(() => false)
|
||||||
|
const permission = granted ? "granted" : await requestPermission().catch(() => "denied")
|
||||||
|
if (permission !== "granted") return
|
||||||
|
|
||||||
|
const win = getCurrentWindow()
|
||||||
|
const focused = await win.isFocused().catch(() => document.hasFocus())
|
||||||
|
if (focused) return
|
||||||
|
|
||||||
|
await Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
const notification = new Notification(title, { body: description ?? "" })
|
||||||
|
notification.onclick = () => {
|
||||||
|
const win = getCurrentWindow()
|
||||||
|
void win.show().catch(() => undefined)
|
||||||
|
void win.unminimize().catch(() => undefined)
|
||||||
|
void win.setFocus().catch(() => undefined)
|
||||||
|
if (href) {
|
||||||
|
window.history.pushState(null, "", href)
|
||||||
|
window.dispatchEvent(new PopStateEvent("popstate"))
|
||||||
|
}
|
||||||
|
notification.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => undefined)
|
||||||
|
},
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
fetch: tauriFetch,
|
fetch: tauriFetch,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ export default defineConfig({
|
|||||||
//
|
//
|
||||||
// 1. prevent Vite from obscuring rust errors
|
// 1. prevent Vite from obscuring rust errors
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
build: {
|
esbuild: {
|
||||||
sourcemap: true,
|
// Improves production stack traces
|
||||||
|
keepNames: true,
|
||||||
},
|
},
|
||||||
|
// build: {
|
||||||
|
// sourcemap: true,
|
||||||
|
// },
|
||||||
// 2. tauri expects a fixed port, fail if that port is not available
|
// 2. tauri expects a fixed port, fail if that port is not available
|
||||||
server: {
|
server: {
|
||||||
port: 1420,
|
port: 1420,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { FileRoutes } from "@solidjs/start/router"
|
|||||||
import { Font } from "@opencode-ai/ui/font"
|
import { Font } from "@opencode-ai/ui/font"
|
||||||
import { MetaProvider } from "@solidjs/meta"
|
import { MetaProvider } from "@solidjs/meta"
|
||||||
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
||||||
|
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Suspense } from "solid-js"
|
import { Suspense } from "solid-js"
|
||||||
import "./app.css"
|
import "./app.css"
|
||||||
import { Favicon } from "@opencode-ai/ui/favicon"
|
import { Favicon } from "@opencode-ai/ui/favicon"
|
||||||
@@ -12,11 +13,13 @@ export default function App() {
|
|||||||
<Router
|
<Router
|
||||||
root={(props) => (
|
root={(props) => (
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<MarkedProvider>
|
<DialogProvider>
|
||||||
<Favicon />
|
<MarkedProvider>
|
||||||
<Font />
|
<Favicon />
|
||||||
<Suspense>{props.children}</Suspense>
|
<Font />
|
||||||
</MarkedProvider>
|
<Suspense>{props.children}</Suspense>
|
||||||
|
</MarkedProvider>
|
||||||
|
</DialogProvider>
|
||||||
</MetaProvider>
|
</MetaProvider>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -162,11 +162,20 @@ export default function () {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
fallback={(e) => {
|
fallback={(error) => {
|
||||||
|
if (SessionDataMissingError.isInstance(error)) {
|
||||||
|
return <NotFound />
|
||||||
|
}
|
||||||
|
console.error(error)
|
||||||
|
const details = error instanceof Error ? (error.stack ?? error.message) : String(error)
|
||||||
return (
|
return (
|
||||||
<Show when={e.message === "SessionDataMissingError"}>
|
<div class="min-h-screen w-full bg-background-base text-text-base flex flex-col items-center justify-center gap-4 p-6 text-center">
|
||||||
<NotFound />
|
<p class="text-16-medium">Unable to render this share.</p>
|
||||||
</Show>
|
<p class="text-14-regular text-text-weaker">Check the console for more details.</p>
|
||||||
|
<pre class="text-12-mono text-left whitespace-pre-wrap break-words w-full max-w-200 bg-background-stronger rounded-md p-4">
|
||||||
|
{details}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
id = "opencode"
|
id = "opencode"
|
||||||
name = "OpenCode"
|
name = "OpenCode"
|
||||||
description = "The open source coding agent."
|
description = "The open source coding agent."
|
||||||
version = "1.0.208"
|
version = "1.0.217"
|
||||||
schema_version = 1
|
schema_version = 1
|
||||||
authors = ["Anomaly"]
|
authors = ["Anomaly"]
|
||||||
repository = "https://github.com/sst/opencode"
|
repository = "https://github.com/sst/opencode"
|
||||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
|||||||
icon = "./icons/opencode.svg"
|
icon = "./icons/opencode.svg"
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-aarch64]
|
[agent_servers.opencode.targets.darwin-aarch64]
|
||||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.208/opencode-darwin-arm64.zip"
|
archive = "https://github.com/sst/opencode/releases/download/v1.0.217/opencode-darwin-arm64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.darwin-x86_64]
|
[agent_servers.opencode.targets.darwin-x86_64]
|
||||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.208/opencode-darwin-x64.zip"
|
archive = "https://github.com/sst/opencode/releases/download/v1.0.217/opencode-darwin-x64.zip"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-aarch64]
|
[agent_servers.opencode.targets.linux-aarch64]
|
||||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.208/opencode-linux-arm64.tar.gz"
|
archive = "https://github.com/sst/opencode/releases/download/v1.0.217/opencode-linux-arm64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.linux-x86_64]
|
[agent_servers.opencode.targets.linux-x86_64]
|
||||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.208/opencode-linux-x64.tar.gz"
|
archive = "https://github.com/sst/opencode/releases/download/v1.0.217/opencode-linux-x64.tar.gz"
|
||||||
cmd = "./opencode"
|
cmd = "./opencode"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|
||||||
[agent_servers.opencode.targets.windows-x86_64]
|
[agent_servers.opencode.targets.windows-x86_64]
|
||||||
archive = "https://github.com/sst/opencode/releases/download/v1.0.208/opencode-windows-x64.zip"
|
archive = "https://github.com/sst/opencode/releases/download/v1.0.217/opencode-windows-x64.zip"
|
||||||
cmd = "./opencode.exe"
|
cmd = "./opencode.exe"
|
||||||
args = ["acp"]
|
args = ["acp"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ preload = ["@opentui/solid/preload"]
|
|||||||
|
|
||||||
[test]
|
[test]
|
||||||
preload = ["./test/preload.ts"]
|
preload = ["./test/preload.ts"]
|
||||||
|
timeout = 10000 # 10 seconds (default is 5000ms)
|
||||||
# Enable code coverage
|
# Enable code coverage
|
||||||
coverage = true
|
coverage = true
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -51,18 +51,17 @@
|
|||||||
"@agentclientprotocol/sdk": "0.5.1",
|
"@agentclientprotocol/sdk": "0.5.1",
|
||||||
"@ai-sdk/amazon-bedrock": "3.0.57",
|
"@ai-sdk/amazon-bedrock": "3.0.57",
|
||||||
"@ai-sdk/anthropic": "2.0.56",
|
"@ai-sdk/anthropic": "2.0.56",
|
||||||
"@ai-sdk/azure": "2.0.73",
|
"@ai-sdk/azure": "2.0.82",
|
||||||
"@ai-sdk/cerebras": "1.0.33",
|
"@ai-sdk/cerebras": "1.0.33",
|
||||||
"@ai-sdk/cohere": "2.0.21",
|
"@ai-sdk/cohere": "2.0.21",
|
||||||
"@ai-sdk/deepinfra": "1.0.30",
|
"@ai-sdk/deepinfra": "1.0.30",
|
||||||
"@ai-sdk/gateway": "2.0.23",
|
"@ai-sdk/gateway": "2.0.23",
|
||||||
"@ai-sdk/google": "2.0.44",
|
"@ai-sdk/google": "2.0.49",
|
||||||
"@ai-sdk/google-vertex": "3.0.81",
|
"@ai-sdk/google-vertex": "3.0.81",
|
||||||
"@ai-sdk/groq": "2.0.33",
|
"@ai-sdk/groq": "2.0.33",
|
||||||
"@ai-sdk/mcp": "0.0.8",
|
|
||||||
"@ai-sdk/mistral": "2.0.26",
|
"@ai-sdk/mistral": "2.0.26",
|
||||||
"@ai-sdk/openai": "2.0.71",
|
"@ai-sdk/openai": "2.0.71",
|
||||||
"@ai-sdk/openai-compatible": "1.0.27",
|
"@ai-sdk/openai-compatible": "1.0.29",
|
||||||
"@ai-sdk/perplexity": "2.0.22",
|
"@ai-sdk/perplexity": "2.0.22",
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "2.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.19",
|
"@ai-sdk/provider-utils": "3.0.19",
|
||||||
@@ -80,8 +79,8 @@
|
|||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/util": "workspace:*",
|
"@opencode-ai/util": "workspace:*",
|
||||||
"@openrouter/ai-sdk-provider": "1.5.2",
|
"@openrouter/ai-sdk-provider": "1.5.2",
|
||||||
"@opentui/core": "0.1.63",
|
"@opentui/core": "0.1.67",
|
||||||
"@opentui/solid": "0.1.63",
|
"@opentui/solid": "0.1.67",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@solid-primitives/event-bus": "1.1.2",
|
"@solid-primitives/event-bus": "1.1.2",
|
||||||
|
|||||||
@@ -235,5 +235,19 @@ export default {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
filetype: "nix",
|
||||||
|
// TODO: Replace with official tree-sitter-nix WASM when published
|
||||||
|
// See: https://github.com/nix-community/tree-sitter-nix/issues/66
|
||||||
|
wasm: "https://github.com/ast-grep/ast-grep.github.io/raw/40b84530640aa83a0d34a20a2b0623d7b8e5ea97/website/public/parsers/tree-sitter-nix.wasm",
|
||||||
|
queries: {
|
||||||
|
highlights: [
|
||||||
|
"https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/nix/highlights.scm",
|
||||||
|
],
|
||||||
|
locals: [
|
||||||
|
"https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/nix/locals.scm",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -349,6 +349,12 @@ export const AuthLoginCommand = cmd({
|
|||||||
prompts.log.info("You can create an api key at https://vercel.link/ai-gateway-token")
|
prompts.log.info("You can create an api key at https://vercel.link/ai-gateway-token")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (["cloudflare", "cloudflare-ai-gateway"].includes(provider)) {
|
||||||
|
prompts.log.info(
|
||||||
|
"Cloudflare AI Gateway can be configured with CLOUDFLARE_GATEWAY_ID, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_API_TOKEN environment variables. Read more: https://opencode.ai/docs/providers/#cloudflare-ai-gateway",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const key = await prompts.password({
|
const key = await prompts.password({
|
||||||
message: "Enter your API key",
|
message: "Enter your API key",
|
||||||
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
|
||||||
|
|||||||
@@ -372,6 +372,15 @@ function App() {
|
|||||||
local.agent.move(1)
|
local.agent.move(1)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Variant cycle",
|
||||||
|
value: "variant.cycle",
|
||||||
|
keybind: "variant_cycle",
|
||||||
|
category: "Agent",
|
||||||
|
onSelect: () => {
|
||||||
|
local.model.variant.cycle()
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Agent cycle reverse",
|
title: "Agent cycle reverse",
|
||||||
value: "agent.cycle.reverse",
|
value: "agent.cycle.reverse",
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
|
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
|
||||||
)
|
)
|
||||||
if (inFavorites) return false
|
if (inFavorites) return false
|
||||||
const inRecents = recents.some(
|
const inRecents = recentList.some(
|
||||||
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
|
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
|
||||||
)
|
)
|
||||||
if (inRecents) return false
|
if (inRecents) return false
|
||||||
|
|||||||
@@ -167,6 +167,13 @@ export function Prompt(props: PromptProps) {
|
|||||||
if (!props.disabled) input.cursorColor = theme.text
|
if (!props.disabled) input.cursorColor = theme.text
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const lastUserMessage = createMemo(() => {
|
||||||
|
if (!props.sessionID) return undefined
|
||||||
|
const messages = sync.data.message[props.sessionID]
|
||||||
|
if (!messages) return undefined
|
||||||
|
return messages.findLast((m) => m.role === "user")
|
||||||
|
})
|
||||||
|
|
||||||
const [store, setStore] = createStore<{
|
const [store, setStore] = createStore<{
|
||||||
prompt: PromptInfo
|
prompt: PromptInfo
|
||||||
mode: "normal" | "shell"
|
mode: "normal" | "shell"
|
||||||
@@ -184,6 +191,23 @@ export function Prompt(props: PromptProps) {
|
|||||||
interrupt: 0,
|
interrupt: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Initialize agent/model/variant from last user message when session changes
|
||||||
|
let syncedSessionID: string | undefined
|
||||||
|
createEffect(() => {
|
||||||
|
const sessionID = props.sessionID
|
||||||
|
const msg = lastUserMessage()
|
||||||
|
|
||||||
|
if (sessionID !== syncedSessionID) {
|
||||||
|
if (!sessionID || !msg) return
|
||||||
|
|
||||||
|
syncedSessionID = sessionID
|
||||||
|
|
||||||
|
if (msg.agent) local.agent.set(msg.agent)
|
||||||
|
if (msg.model) local.model.set(msg.model)
|
||||||
|
if (msg.variant) local.model.variant.set(msg.variant)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
command.register(() => {
|
command.register(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -562,6 +586,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
|
|
||||||
// Capture mode before it gets reset
|
// Capture mode before it gets reset
|
||||||
const currentMode = store.mode
|
const currentMode = store.mode
|
||||||
|
const variant = local.model.variant.current()
|
||||||
|
|
||||||
if (store.mode === "shell") {
|
if (store.mode === "shell") {
|
||||||
sdk.client.session.shell({
|
sdk.client.session.shell({
|
||||||
@@ -590,6 +615,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
agent: local.agent.current().name,
|
agent: local.agent.current().name,
|
||||||
model: `${selectedModel.providerID}/${selectedModel.modelID}`,
|
model: `${selectedModel.providerID}/${selectedModel.modelID}`,
|
||||||
messageID,
|
messageID,
|
||||||
|
variant,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
sdk.client.session.prompt({
|
sdk.client.session.prompt({
|
||||||
@@ -598,6 +624,7 @@ export function Prompt(props: PromptProps) {
|
|||||||
messageID,
|
messageID,
|
||||||
agent: local.agent.current().name,
|
agent: local.agent.current().name,
|
||||||
model: selectedModel,
|
model: selectedModel,
|
||||||
|
variant,
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
id: Identifier.ascending("part"),
|
id: Identifier.ascending("part"),
|
||||||
@@ -718,6 +745,13 @@ export function Prompt(props: PromptProps) {
|
|||||||
return local.agent.color(local.agent.current().name)
|
return local.agent.color(local.agent.current().name)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const showVariant = createMemo(() => {
|
||||||
|
const variants = local.model.variant.list()
|
||||||
|
if (variants.length === 0) return false
|
||||||
|
const current = local.model.variant.current()
|
||||||
|
return !!current
|
||||||
|
})
|
||||||
|
|
||||||
const spinnerDef = createMemo(() => {
|
const spinnerDef = createMemo(() => {
|
||||||
const color = local.agent.color(local.agent.current().name)
|
const color = local.agent.color(local.agent.current().name)
|
||||||
return {
|
return {
|
||||||
@@ -958,6 +992,12 @@ export function Prompt(props: PromptProps) {
|
|||||||
{local.model.parsed().model}
|
{local.model.parsed().model}
|
||||||
</text>
|
</text>
|
||||||
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
|
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
|
||||||
|
<Show when={showVariant()}>
|
||||||
|
<text fg={theme.textMuted}>·</text>
|
||||||
|
<text>
|
||||||
|
<span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span>
|
||||||
|
</text>
|
||||||
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
|
|||||||
@@ -33,24 +33,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically update model when agent changes
|
|
||||||
createEffect(() => {
|
|
||||||
const value = agent.current()
|
|
||||||
if (value.model) {
|
|
||||||
if (isModelValid(value.model))
|
|
||||||
model.set({
|
|
||||||
providerID: value.model.providerID,
|
|
||||||
modelID: value.model.modelID,
|
|
||||||
})
|
|
||||||
else
|
|
||||||
toast.show({
|
|
||||||
variant: "warning",
|
|
||||||
message: `Agent ${value.name}'s configured model ${value.model.providerID}/${value.model.modelID} is not valid`,
|
|
||||||
duration: 3000,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const agent = iife(() => {
|
const agent = iife(() => {
|
||||||
const agents = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent" && !x.hidden))
|
const agents = createMemo(() => sync.data.agent.filter((x) => x.mode !== "subagent" && !x.hidden))
|
||||||
const [agentStore, setAgentStore] = createStore<{
|
const [agentStore, setAgentStore] = createStore<{
|
||||||
@@ -120,11 +102,13 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
providerID: string
|
providerID: string
|
||||||
modelID: string
|
modelID: string
|
||||||
}[]
|
}[]
|
||||||
|
variant: Record<string, string | undefined>
|
||||||
}>({
|
}>({
|
||||||
ready: false,
|
ready: false,
|
||||||
model: {},
|
model: {},
|
||||||
recent: [],
|
recent: [],
|
||||||
favorite: [],
|
favorite: [],
|
||||||
|
variant: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
const file = Bun.file(path.join(Global.Path.state, "model.json"))
|
const file = Bun.file(path.join(Global.Path.state, "model.json"))
|
||||||
@@ -135,6 +119,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
recent: modelStore.recent,
|
recent: modelStore.recent,
|
||||||
favorite: modelStore.favorite,
|
favorite: modelStore.favorite,
|
||||||
|
variant: modelStore.variant,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -144,6 +129,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
.then((x) => {
|
.then((x) => {
|
||||||
if (Array.isArray(x.recent)) setModelStore("recent", x.recent)
|
if (Array.isArray(x.recent)) setModelStore("recent", x.recent)
|
||||||
if (Array.isArray(x.favorite)) setModelStore("favorite", x.favorite)
|
if (Array.isArray(x.favorite)) setModelStore("favorite", x.favorite)
|
||||||
|
if (typeof x.variant === "object" && x.variant !== null) setModelStore("variant", x.variant)
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -218,6 +204,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
return {
|
return {
|
||||||
provider: "Connect a provider",
|
provider: "Connect a provider",
|
||||||
model: "No provider selected",
|
model: "No provider selected",
|
||||||
|
reasoning: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const provider = sync.data.provider.find((x) => x.id === value.providerID)
|
const provider = sync.data.provider.find((x) => x.id === value.providerID)
|
||||||
@@ -225,6 +212,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
return {
|
return {
|
||||||
provider: provider?.name ?? value.providerID,
|
provider: provider?.name ?? value.providerID,
|
||||||
model: info?.name ?? value.modelID,
|
model: info?.name ?? value.modelID,
|
||||||
|
reasoning: info?.capabilities?.reasoning ?? false,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
cycle(direction: 1 | -1) {
|
cycle(direction: 1 | -1) {
|
||||||
@@ -267,7 +255,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
setModelStore("model", agent.current().name, { ...next })
|
setModelStore("model", agent.current().name, { ...next })
|
||||||
const uniq = uniqueBy([next, ...modelStore.recent], (x) => x.providerID + x.modelID)
|
const uniq = uniqueBy([next, ...modelStore.recent], (x) => x.providerID + x.modelID)
|
||||||
if (uniq.length > 10) uniq.pop()
|
if (uniq.length > 10) uniq.pop()
|
||||||
setModelStore("recent", uniq)
|
setModelStore(
|
||||||
|
"recent",
|
||||||
|
uniq.map((x) => ({ providerID: x.providerID, modelID: x.modelID })),
|
||||||
|
)
|
||||||
save()
|
save()
|
||||||
},
|
},
|
||||||
set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) {
|
set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) {
|
||||||
@@ -284,7 +275,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
if (options?.recent) {
|
if (options?.recent) {
|
||||||
const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID)
|
const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID)
|
||||||
if (uniq.length > 10) uniq.pop()
|
if (uniq.length > 10) uniq.pop()
|
||||||
setModelStore("recent", uniq)
|
setModelStore(
|
||||||
|
"recent",
|
||||||
|
uniq.map((x) => ({ providerID: x.providerID, modelID: x.modelID })),
|
||||||
|
)
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -305,10 +299,53 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
const next = exists
|
const next = exists
|
||||||
? modelStore.favorite.filter((x) => x.providerID !== model.providerID || x.modelID !== model.modelID)
|
? modelStore.favorite.filter((x) => x.providerID !== model.providerID || x.modelID !== model.modelID)
|
||||||
: [model, ...modelStore.favorite]
|
: [model, ...modelStore.favorite]
|
||||||
setModelStore("favorite", next)
|
setModelStore(
|
||||||
|
"favorite",
|
||||||
|
next.map((x) => ({ providerID: x.providerID, modelID: x.modelID })),
|
||||||
|
)
|
||||||
save()
|
save()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
variant: {
|
||||||
|
current() {
|
||||||
|
const m = currentModel()
|
||||||
|
if (!m) return undefined
|
||||||
|
const key = `${m.providerID}/${m.modelID}`
|
||||||
|
return modelStore.variant[key]
|
||||||
|
},
|
||||||
|
list() {
|
||||||
|
const m = currentModel()
|
||||||
|
if (!m) return []
|
||||||
|
const provider = sync.data.provider.find((x) => x.id === m.providerID)
|
||||||
|
const info = provider?.models[m.modelID]
|
||||||
|
if (!info?.variants) return []
|
||||||
|
return Object.entries(info.variants)
|
||||||
|
.filter(([_, v]) => !v.disabled)
|
||||||
|
.map(([name]) => name)
|
||||||
|
},
|
||||||
|
set(value: string | undefined) {
|
||||||
|
const m = currentModel()
|
||||||
|
if (!m) return
|
||||||
|
const key = `${m.providerID}/${m.modelID}`
|
||||||
|
setModelStore("variant", key, value)
|
||||||
|
save()
|
||||||
|
},
|
||||||
|
cycle() {
|
||||||
|
const variants = this.list()
|
||||||
|
if (variants.length === 0) return
|
||||||
|
const current = this.current()
|
||||||
|
if (!current) {
|
||||||
|
this.set(variants[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const index = variants.indexOf(current)
|
||||||
|
if (index === -1 || index === variants.length - 1) {
|
||||||
|
this.set(undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.set(variants[index + 1])
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -329,6 +366,24 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Automatically update model when agent changes
|
||||||
|
createEffect(() => {
|
||||||
|
const value = agent.current()
|
||||||
|
if (value.model) {
|
||||||
|
if (isModelValid(value.model))
|
||||||
|
model.set({
|
||||||
|
providerID: value.model.providerID,
|
||||||
|
modelID: value.model.modelID,
|
||||||
|
})
|
||||||
|
else
|
||||||
|
toast.show({
|
||||||
|
variant: "warning",
|
||||||
|
message: `Agent ${value.name}'s configured model ${value.model.providerID}/${value.model.modelID} is not valid`,
|
||||||
|
duration: 3000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
model,
|
model,
|
||||||
agent,
|
agent,
|
||||||
|
|||||||
@@ -115,11 +115,12 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
setStore("selected", currentIndex)
|
setStore("selected", currentIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scroll.scrollTo(0)
|
scroll?.scrollTo(0)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
function move(direction: number) {
|
function move(direction: number) {
|
||||||
|
if (flat().length === 0) return
|
||||||
let next = store.selected + direction
|
let next = store.selected + direction
|
||||||
if (next < 0) next = flat().length - 1
|
if (next < 0) next = flat().length - 1
|
||||||
if (next >= flat().length) next = 0
|
if (next >= flat().length) next = 0
|
||||||
@@ -129,6 +130,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
function moveTo(next: number) {
|
function moveTo(next: number) {
|
||||||
setStore("selected", next)
|
setStore("selected", next)
|
||||||
props.onMove?.(selected()!)
|
props.onMove?.(selected()!)
|
||||||
|
if (!scroll) return
|
||||||
const target = scroll.getChildren().find((child) => {
|
const target = scroll.getChildren().find((child) => {
|
||||||
return child.id === JSON.stringify(selected()?.value)
|
return child.id === JSON.stringify(selected()?.value)
|
||||||
})
|
})
|
||||||
@@ -172,7 +174,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let scroll: ScrollBoxRenderable
|
let scroll: ScrollBoxRenderable | undefined
|
||||||
const ref: DialogSelectRef<T> = {
|
const ref: DialogSelectRef<T> = {
|
||||||
get filter() {
|
get filter() {
|
||||||
return store.filter
|
return store.filter
|
||||||
@@ -213,61 +215,70 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
<scrollbox
|
<Show
|
||||||
paddingLeft={1}
|
when={grouped().length > 0}
|
||||||
paddingRight={1}
|
fallback={
|
||||||
scrollbarOptions={{ visible: false }}
|
<box paddingLeft={4} paddingRight={4} paddingTop={1}>
|
||||||
ref={(r: ScrollBoxRenderable) => (scroll = r)}
|
<text fg={theme.textMuted}>No results found</text>
|
||||||
maxHeight={height()}
|
</box>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<For each={grouped()}>
|
<scrollbox
|
||||||
{([category, options], index) => (
|
paddingLeft={1}
|
||||||
<>
|
paddingRight={1}
|
||||||
<Show when={category}>
|
scrollbarOptions={{ visible: false }}
|
||||||
<box paddingTop={index() > 0 ? 1 : 0} paddingLeft={3}>
|
ref={(r: ScrollBoxRenderable) => (scroll = r)}
|
||||||
<text fg={theme.accent} attributes={TextAttributes.BOLD}>
|
maxHeight={height()}
|
||||||
{category}
|
>
|
||||||
</text>
|
<For each={grouped()}>
|
||||||
</box>
|
{([category, options], index) => (
|
||||||
</Show>
|
<>
|
||||||
<For each={options}>
|
<Show when={category}>
|
||||||
{(option) => {
|
<box paddingTop={index() > 0 ? 1 : 0} paddingLeft={3}>
|
||||||
const active = createMemo(() => isDeepEqual(option.value, selected()?.value))
|
<text fg={theme.accent} attributes={TextAttributes.BOLD}>
|
||||||
const current = createMemo(() => isDeepEqual(option.value, props.current))
|
{category}
|
||||||
return (
|
</text>
|
||||||
<box
|
</box>
|
||||||
id={JSON.stringify(option.value)}
|
</Show>
|
||||||
flexDirection="row"
|
<For each={options}>
|
||||||
onMouseUp={() => {
|
{(option) => {
|
||||||
option.onSelect?.(dialog)
|
const active = createMemo(() => isDeepEqual(option.value, selected()?.value))
|
||||||
props.onSelect?.(option)
|
const current = createMemo(() => isDeepEqual(option.value, props.current))
|
||||||
}}
|
return (
|
||||||
onMouseOver={() => {
|
<box
|
||||||
const index = filtered().findIndex((x) => isDeepEqual(x.value, option.value))
|
id={JSON.stringify(option.value)}
|
||||||
if (index === -1) return
|
flexDirection="row"
|
||||||
moveTo(index)
|
onMouseUp={() => {
|
||||||
}}
|
option.onSelect?.(dialog)
|
||||||
backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)}
|
props.onSelect?.(option)
|
||||||
paddingLeft={current() || option.gutter ? 1 : 3}
|
}}
|
||||||
paddingRight={3}
|
onMouseOver={() => {
|
||||||
gap={1}
|
const index = filtered().findIndex((x) => isDeepEqual(x.value, option.value))
|
||||||
>
|
if (index === -1) return
|
||||||
<Option
|
moveTo(index)
|
||||||
title={option.title}
|
}}
|
||||||
footer={option.footer}
|
backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)}
|
||||||
description={option.description !== category ? option.description : undefined}
|
paddingLeft={current() || option.gutter ? 1 : 3}
|
||||||
active={active()}
|
paddingRight={3}
|
||||||
current={current()}
|
gap={1}
|
||||||
gutter={option.gutter}
|
>
|
||||||
/>
|
<Option
|
||||||
</box>
|
title={option.title}
|
||||||
)
|
footer={option.footer}
|
||||||
}}
|
description={option.description !== category ? option.description : undefined}
|
||||||
</For>
|
active={active()}
|
||||||
</>
|
current={current()}
|
||||||
)}
|
gutter={option.gutter}
|
||||||
</For>
|
/>
|
||||||
</scrollbox>
|
</box>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</scrollbox>
|
||||||
|
</Show>
|
||||||
<Show when={keybinds().length} fallback={<box flexShrink={0} />}>
|
<Show when={keybinds().length} fallback={<box flexShrink={0} />}>
|
||||||
<box paddingRight={2} paddingLeft={4} flexDirection="row" gap={2} flexShrink={0} paddingTop={1}>
|
<box paddingRight={2} paddingLeft={4} flexDirection="row" gap={2} flexShrink={0} paddingTop={1}>
|
||||||
<For each={keybinds()}>
|
<For each={keybinds()}>
|
||||||
|
|||||||
@@ -490,6 +490,7 @@ export namespace Config {
|
|||||||
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
|
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
|
||||||
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
|
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
|
||||||
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
|
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
|
||||||
|
variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
|
||||||
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
|
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
|
||||||
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
|
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
|
||||||
input_submit: z.string().optional().default("return").describe("Submit input"),
|
input_submit: z.string().optional().default("return").describe("Submit input"),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Filesystem } from "../util/filesystem"
|
|||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { Ripgrep } from "./ripgrep"
|
import { Ripgrep } from "./ripgrep"
|
||||||
import fuzzysort from "fuzzysort"
|
import fuzzysort from "fuzzysort"
|
||||||
|
import { Global } from "../global"
|
||||||
|
|
||||||
export namespace File {
|
export namespace File {
|
||||||
const log = Log.create({ service: "file" })
|
const log = Log.create({ service: "file" })
|
||||||
@@ -122,10 +123,49 @@ export namespace File {
|
|||||||
type Entry = { files: string[]; dirs: string[] }
|
type Entry = { files: string[]; dirs: string[] }
|
||||||
let cache: Entry = { files: [], dirs: [] }
|
let cache: Entry = { files: [], dirs: [] }
|
||||||
let fetching = false
|
let fetching = false
|
||||||
|
|
||||||
|
const isGlobalHome = Instance.directory === Global.Path.home && Instance.project.id === "global"
|
||||||
|
|
||||||
const fn = async (result: Entry) => {
|
const fn = async (result: Entry) => {
|
||||||
// Disable scanning if in root of file system
|
// Disable scanning if in root of file system
|
||||||
if (Instance.directory === path.parse(Instance.directory).root) return
|
if (Instance.directory === path.parse(Instance.directory).root) return
|
||||||
fetching = true
|
fetching = true
|
||||||
|
|
||||||
|
if (isGlobalHome) {
|
||||||
|
const dirs = new Set<string>()
|
||||||
|
const ignore = new Set<string>()
|
||||||
|
|
||||||
|
if (process.platform === "darwin") ignore.add("Library")
|
||||||
|
if (process.platform === "win32") ignore.add("AppData")
|
||||||
|
|
||||||
|
const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"])
|
||||||
|
const shouldIgnore = (name: string) => name.startsWith(".") || ignore.has(name)
|
||||||
|
const shouldIgnoreNested = (name: string) => name.startsWith(".") || ignoreNested.has(name)
|
||||||
|
|
||||||
|
const top = await fs.promises
|
||||||
|
.readdir(Instance.directory, { withFileTypes: true })
|
||||||
|
.catch(() => [] as fs.Dirent[])
|
||||||
|
|
||||||
|
for (const entry of top) {
|
||||||
|
if (!entry.isDirectory()) continue
|
||||||
|
if (shouldIgnore(entry.name)) continue
|
||||||
|
dirs.add(entry.name + "/")
|
||||||
|
|
||||||
|
const base = path.join(Instance.directory, entry.name)
|
||||||
|
const children = await fs.promises.readdir(base, { withFileTypes: true }).catch(() => [] as fs.Dirent[])
|
||||||
|
for (const child of children) {
|
||||||
|
if (!child.isDirectory()) continue
|
||||||
|
if (shouldIgnoreNested(child.name)) continue
|
||||||
|
dirs.add(entry.name + "/" + child.name + "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.dirs = Array.from(dirs).toSorted()
|
||||||
|
cache = result
|
||||||
|
fetching = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const set = new Set<string>()
|
const set = new Set<string>()
|
||||||
for await (const file of Ripgrep.files({ cwd: Instance.directory })) {
|
for await (const file of Ripgrep.files({ cwd: Instance.directory })) {
|
||||||
result.files.push(file)
|
result.files.push(file)
|
||||||
@@ -329,15 +369,43 @@ export namespace File {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function search(input: { query: string; limit?: number; dirs?: boolean }) {
|
export async function search(input: { query: string; limit?: number; dirs?: boolean; type?: "file" | "directory" }) {
|
||||||
log.info("search", { query: input.query })
|
const query = input.query.trim()
|
||||||
const limit = input.limit ?? 100
|
const limit = input.limit ?? 100
|
||||||
|
const kind = input.type ?? (input.dirs === false ? "file" : "all")
|
||||||
|
log.info("search", { query, kind })
|
||||||
|
|
||||||
const result = await state().then((x) => x.files())
|
const result = await state().then((x) => x.files())
|
||||||
if (!input.query)
|
|
||||||
return input.dirs !== false ? result.dirs.toSorted().slice(0, limit) : result.files.slice(0, limit)
|
const hidden = (item: string) => {
|
||||||
const items = input.dirs !== false ? [...result.files, ...result.dirs] : result.files
|
const normalized = item.replaceAll("\\", "/").replace(/\/+$/, "")
|
||||||
const sorted = fuzzysort.go(input.query, items, { limit: limit }).map((r) => r.target)
|
return normalized.split("/").some((p) => p.startsWith(".") && p.length > 1)
|
||||||
log.info("search", { query: input.query, results: sorted.length })
|
}
|
||||||
return sorted
|
const preferHidden = query.startsWith(".") || query.includes("/.")
|
||||||
|
const sortHiddenLast = (items: string[]) => {
|
||||||
|
if (preferHidden) return items
|
||||||
|
const visible: string[] = []
|
||||||
|
const hiddenItems: string[] = []
|
||||||
|
for (const item of items) {
|
||||||
|
const isHidden = hidden(item)
|
||||||
|
if (isHidden) hiddenItems.push(item)
|
||||||
|
if (!isHidden) visible.push(item)
|
||||||
|
}
|
||||||
|
return [...visible, ...hiddenItems]
|
||||||
|
}
|
||||||
|
if (!query) {
|
||||||
|
if (kind === "file") return result.files.slice(0, limit)
|
||||||
|
return sortHiddenLast(result.dirs.toSorted()).slice(0, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
const items =
|
||||||
|
kind === "file" ? result.files : kind === "directory" ? result.dirs : [...result.files, ...result.dirs]
|
||||||
|
|
||||||
|
const searchLimit = kind === "directory" && !preferHidden ? limit * 20 : limit
|
||||||
|
const sorted = fuzzysort.go(query, items, { limit: searchLimit }).map((r) => r.target)
|
||||||
|
const output = kind === "directory" ? sortHiddenLast(sorted).slice(0, limit) : sorted
|
||||||
|
|
||||||
|
log.info("search", { query, kind, results: output.length })
|
||||||
|
return output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,8 +205,17 @@ export namespace Ripgrep {
|
|||||||
return filepath
|
return filepath
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function* files(input: { cwd: string; glob?: string[] }) {
|
export async function* files(input: {
|
||||||
const args = [await filepath(), "--files", "--follow", "--hidden", "--glob=!.git/*"]
|
cwd: string
|
||||||
|
glob?: string[]
|
||||||
|
hidden?: boolean
|
||||||
|
follow?: boolean
|
||||||
|
maxDepth?: number
|
||||||
|
}) {
|
||||||
|
const args = [await filepath(), "--files", "--glob=!.git/*"]
|
||||||
|
if (input.follow !== false) args.push("--follow")
|
||||||
|
if (input.hidden !== false) args.push("--hidden")
|
||||||
|
if (input.maxDepth !== undefined) args.push(`--max-depth=${input.maxDepth}`)
|
||||||
if (input.glob) {
|
if (input.glob) {
|
||||||
for (const g of input.glob) {
|
for (const g of input.glob) {
|
||||||
args.push(`--glob=${g}`)
|
args.push(`--glob=${g}`)
|
||||||
|
|||||||
@@ -9,9 +9,13 @@ import path from "path"
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { createWrapper } from "@parcel/watcher/wrapper"
|
import { createWrapper } from "@parcel/watcher/wrapper"
|
||||||
import { lazy } from "@/util/lazy"
|
import { lazy } from "@/util/lazy"
|
||||||
|
import { withTimeout } from "@/util/timeout"
|
||||||
import type ParcelWatcher from "@parcel/watcher"
|
import type ParcelWatcher from "@parcel/watcher"
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import { Flag } from "@/flag/flag"
|
import { Flag } from "@/flag/flag"
|
||||||
|
import { readdir } from "fs/promises"
|
||||||
|
|
||||||
|
const SUBSCRIBE_TIMEOUT_MS = 10_000
|
||||||
|
|
||||||
declare const OPENCODE_LIBC: string | undefined
|
declare const OPENCODE_LIBC: string | undefined
|
||||||
|
|
||||||
@@ -63,12 +67,16 @@ export namespace FileWatcher {
|
|||||||
const cfgIgnores = cfg.watcher?.ignore ?? []
|
const cfgIgnores = cfg.watcher?.ignore ?? []
|
||||||
|
|
||||||
if (Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) {
|
if (Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) {
|
||||||
subs.push(
|
const pending = watcher().subscribe(Instance.directory, subscribe, {
|
||||||
await watcher().subscribe(Instance.directory, subscribe, {
|
ignore: [...FileIgnore.PATTERNS, ...cfgIgnores],
|
||||||
ignore: [...FileIgnore.PATTERNS, ...cfgIgnores],
|
backend,
|
||||||
backend,
|
})
|
||||||
}),
|
const sub = await withTimeout(pending, SUBSCRIBE_TIMEOUT_MS).catch((err) => {
|
||||||
)
|
log.error("failed to subscribe to Instance.directory", { error: err })
|
||||||
|
pending.then((s) => s.unsubscribe()).catch(() => {})
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
if (sub) subs.push(sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
const vcsDir = await $`git rev-parse --git-dir`
|
const vcsDir = await $`git rev-parse --git-dir`
|
||||||
@@ -78,12 +86,18 @@ export namespace FileWatcher {
|
|||||||
.text()
|
.text()
|
||||||
.then((x) => path.resolve(Instance.worktree, x.trim()))
|
.then((x) => path.resolve(Instance.worktree, x.trim()))
|
||||||
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
|
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
|
||||||
subs.push(
|
const gitDirContents = await readdir(vcsDir).catch(() => [])
|
||||||
await watcher().subscribe(vcsDir, subscribe, {
|
const ignoreList = gitDirContents.filter((entry) => entry !== "HEAD")
|
||||||
ignore: ["hooks", "info", "logs", "objects", "refs", "worktrees", "modules", "lfs"],
|
const pending = watcher().subscribe(vcsDir, subscribe, {
|
||||||
backend,
|
ignore: ignoreList,
|
||||||
}),
|
backend,
|
||||||
)
|
})
|
||||||
|
const sub = await withTimeout(pending, SUBSCRIBE_TIMEOUT_MS).catch((err) => {
|
||||||
|
log.error("failed to subscribe to vcsDir", { error: err })
|
||||||
|
pending.then((s) => s.unsubscribe()).catch(() => {})
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
if (sub) subs.push(sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { subs }
|
return { subs }
|
||||||
|
|||||||
@@ -322,3 +322,12 @@ export const shfmt: Info = {
|
|||||||
return Bun.which("shfmt") !== null
|
return Bun.which("shfmt") !== null
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const nixfmt: Info = {
|
||||||
|
name: "nixfmt",
|
||||||
|
command: ["nixfmt", "$FILE"],
|
||||||
|
extensions: [".nix"],
|
||||||
|
async enabled() {
|
||||||
|
return Bun.which("nixfmt") !== null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,14 +12,17 @@ const state = path.join(xdgState!, app)
|
|||||||
|
|
||||||
export namespace Global {
|
export namespace Global {
|
||||||
export const Path = {
|
export const Path = {
|
||||||
home: os.homedir(),
|
// Allow override via OPENCODE_TEST_HOME for test isolation
|
||||||
|
get home() {
|
||||||
|
return process.env.OPENCODE_TEST_HOME || os.homedir()
|
||||||
|
},
|
||||||
data,
|
data,
|
||||||
bin: path.join(data, "bin"),
|
bin: path.join(data, "bin"),
|
||||||
log: path.join(data, "log"),
|
log: path.join(data, "log"),
|
||||||
cache,
|
cache,
|
||||||
config,
|
config,
|
||||||
state,
|
state,
|
||||||
} as const
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export namespace Plugin {
|
|||||||
project: Instance.project,
|
project: Instance.project,
|
||||||
worktree: Instance.worktree,
|
worktree: Instance.worktree,
|
||||||
directory: Instance.directory,
|
directory: Instance.directory,
|
||||||
|
serverUrl: Server.url(),
|
||||||
$: Bun.$,
|
$: Bun.$,
|
||||||
}
|
}
|
||||||
const plugins = [...(config.plugin ?? [])]
|
const plugins = [...(config.plugin ?? [])]
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { createCohere } from "@ai-sdk/cohere"
|
|||||||
import { createGateway } from "@ai-sdk/gateway"
|
import { createGateway } from "@ai-sdk/gateway"
|
||||||
import { createTogetherAI } from "@ai-sdk/togetherai"
|
import { createTogetherAI } from "@ai-sdk/togetherai"
|
||||||
import { createPerplexity } from "@ai-sdk/perplexity"
|
import { createPerplexity } from "@ai-sdk/perplexity"
|
||||||
|
import { ProviderTransform } from "./transform"
|
||||||
|
|
||||||
export namespace Provider {
|
export namespace Provider {
|
||||||
const log = Log.create({ service: "provider" })
|
const log = Log.create({ service: "provider" })
|
||||||
@@ -404,6 +405,16 @@ export namespace Provider {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Variant = z
|
||||||
|
.object({
|
||||||
|
disabled: z.boolean(),
|
||||||
|
})
|
||||||
|
.catchall(z.any())
|
||||||
|
.meta({
|
||||||
|
ref: "Variant",
|
||||||
|
})
|
||||||
|
export type Variant = z.infer<typeof Variant>
|
||||||
|
|
||||||
export const Model = z
|
export const Model = z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
@@ -467,6 +478,7 @@ export namespace Provider {
|
|||||||
options: z.record(z.string(), z.any()),
|
options: z.record(z.string(), z.any()),
|
||||||
headers: z.record(z.string(), z.string()),
|
headers: z.record(z.string(), z.string()),
|
||||||
release_date: z.string(),
|
release_date: z.string(),
|
||||||
|
variants: z.record(z.string(), Variant).optional(),
|
||||||
})
|
})
|
||||||
.meta({
|
.meta({
|
||||||
ref: "Model",
|
ref: "Model",
|
||||||
@@ -489,7 +501,7 @@ export namespace Provider {
|
|||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
|
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
|
||||||
return {
|
const m: Model = {
|
||||||
id: model.id,
|
id: model.id,
|
||||||
providerID: provider.id,
|
providerID: provider.id,
|
||||||
name: model.name,
|
name: model.name,
|
||||||
@@ -546,7 +558,12 @@ export namespace Provider {
|
|||||||
interleaved: model.interleaved ?? false,
|
interleaved: model.interleaved ?? false,
|
||||||
},
|
},
|
||||||
release_date: model.release_date,
|
release_date: model.release_date,
|
||||||
|
variants: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.variants = mapValues(ProviderTransform.variants(m), (v) => ({ disabled: false, ...v }))
|
||||||
|
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
|
export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export namespace ProviderTransform {
|
|||||||
cacheControl: { type: "ephemeral" },
|
cacheControl: { type: "ephemeral" },
|
||||||
},
|
},
|
||||||
openrouter: {
|
openrouter: {
|
||||||
cache_control: { type: "ephemeral" },
|
cacheControl: { type: "ephemeral" },
|
||||||
},
|
},
|
||||||
bedrock: {
|
bedrock: {
|
||||||
cachePoint: { type: "ephemeral" },
|
cachePoint: { type: "ephemeral" },
|
||||||
@@ -243,6 +243,162 @@ export namespace ProviderTransform {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WIDELY_SUPPORTED_EFFORTS = ["low", "medium", "high"]
|
||||||
|
const OPENAI_EFFORTS = ["none", "minimal", ...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
|
||||||
|
|
||||||
|
export function variants(model: Provider.Model) {
|
||||||
|
if (!model.capabilities.reasoning) return {}
|
||||||
|
|
||||||
|
const id = model.id.toLowerCase()
|
||||||
|
if (id.includes("deepseek") || id.includes("minimax") || id.includes("glm") || id.includes("mistral")) return {}
|
||||||
|
|
||||||
|
switch (model.api.npm) {
|
||||||
|
case "@openrouter/ai-sdk-provider":
|
||||||
|
if (!model.id.includes("gpt") && !model.id.includes("gemini-3") && !model.id.includes("grok-4")) return {}
|
||||||
|
return Object.fromEntries(OPENAI_EFFORTS.map((effort) => [effort, { reasoning: { effort } }]))
|
||||||
|
|
||||||
|
// TODO: YOU CANNOT SET max_tokens if this is set!!!
|
||||||
|
case "@ai-sdk/gateway":
|
||||||
|
return Object.fromEntries(OPENAI_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
|
||||||
|
|
||||||
|
case "@ai-sdk/cerebras":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/cerebras
|
||||||
|
case "@ai-sdk/togetherai":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/togetherai
|
||||||
|
case "@ai-sdk/xai":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/xai
|
||||||
|
case "@ai-sdk/deepinfra":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/deepinfra
|
||||||
|
case "@ai-sdk/openai-compatible":
|
||||||
|
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
|
||||||
|
|
||||||
|
case "@ai-sdk/azure":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/azure
|
||||||
|
if (id === "o1-mini") return {}
|
||||||
|
const azureEfforts = ["low", "medium", "high"]
|
||||||
|
if (id.includes("gpt-5")) {
|
||||||
|
azureEfforts.unshift("minimal")
|
||||||
|
}
|
||||||
|
return Object.fromEntries(
|
||||||
|
azureEfforts.map((effort) => [
|
||||||
|
effort,
|
||||||
|
{
|
||||||
|
reasoningEffort: effort,
|
||||||
|
reasoningSummary: "auto",
|
||||||
|
include: ["reasoning.encrypted_content"],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
case "@ai-sdk/openai":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/openai
|
||||||
|
if (id === "gpt-5-pro") return {}
|
||||||
|
const openaiEfforts = ["minimal", ...WIDELY_SUPPORTED_EFFORTS]
|
||||||
|
if (model.release_date >= "2025-11-13") {
|
||||||
|
openaiEfforts.unshift("none")
|
||||||
|
}
|
||||||
|
if (model.release_date >= "2025-12-04") {
|
||||||
|
openaiEfforts.push("xhigh")
|
||||||
|
}
|
||||||
|
return Object.fromEntries(
|
||||||
|
openaiEfforts.map((effort) => [
|
||||||
|
effort,
|
||||||
|
{
|
||||||
|
reasoningEffort: effort,
|
||||||
|
reasoningSummary: "auto",
|
||||||
|
include: ["reasoning.encrypted_content"],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
case "@ai-sdk/anthropic":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/anthropic
|
||||||
|
return {
|
||||||
|
high: {
|
||||||
|
thinking: {
|
||||||
|
type: "enabled",
|
||||||
|
budgetTokens: 16000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
thinking: {
|
||||||
|
type: "enabled",
|
||||||
|
budgetTokens: 31999,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
case "@ai-sdk/amazon-bedrock":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock
|
||||||
|
return Object.fromEntries(
|
||||||
|
WIDELY_SUPPORTED_EFFORTS.map((effort) => [
|
||||||
|
effort,
|
||||||
|
{
|
||||||
|
reasoningConfig: {
|
||||||
|
type: "enabled",
|
||||||
|
maxReasoningEffort: effort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
case "@ai-sdk/google-vertex":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-vertex
|
||||||
|
case "@ai-sdk/google":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/google-generative-ai
|
||||||
|
if (id.includes("2.5")) {
|
||||||
|
return {
|
||||||
|
high: {
|
||||||
|
thinkingConfig: {
|
||||||
|
includeThoughts: true,
|
||||||
|
thinkingBudget: 16000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
thinkingConfig: {
|
||||||
|
includeThoughts: true,
|
||||||
|
thinkingBudget: 24576,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.fromEntries(
|
||||||
|
["low", "high"].map((effort) => [
|
||||||
|
effort,
|
||||||
|
{
|
||||||
|
includeThoughts: true,
|
||||||
|
thinkingLevel: effort,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
case "@ai-sdk/mistral":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/mistral
|
||||||
|
return {}
|
||||||
|
|
||||||
|
case "@ai-sdk/cohere":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/cohere
|
||||||
|
return {}
|
||||||
|
|
||||||
|
case "@ai-sdk/groq":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/groq
|
||||||
|
const groqEffort = ["none", ...WIDELY_SUPPORTED_EFFORTS]
|
||||||
|
return Object.fromEntries(
|
||||||
|
groqEffort.map((effort) => [
|
||||||
|
effort,
|
||||||
|
{
|
||||||
|
includeThoughts: true,
|
||||||
|
thinkingLevel: effort,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
case "@ai-sdk/perplexity":
|
||||||
|
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/perplexity
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
export function options(
|
export function options(
|
||||||
model: Provider.Model,
|
model: Provider.Model,
|
||||||
sessionID: string,
|
sessionID: string,
|
||||||
@@ -302,26 +458,27 @@ export namespace ProviderTransform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function smallOptions(model: Provider.Model) {
|
export function smallOptions(model: Provider.Model) {
|
||||||
const options: Record<string, any> = {}
|
|
||||||
|
|
||||||
if (model.providerID === "openai" || model.api.id.includes("gpt-5")) {
|
if (model.providerID === "openai" || model.api.id.includes("gpt-5")) {
|
||||||
if (model.api.id.includes("5.")) {
|
if (model.api.id.includes("5.")) {
|
||||||
options["reasoningEffort"] = "low"
|
return { reasoningEffort: "low" }
|
||||||
} else {
|
|
||||||
options["reasoningEffort"] = "minimal"
|
|
||||||
}
|
}
|
||||||
|
return { reasoningEffort: "minimal" }
|
||||||
}
|
}
|
||||||
if (model.providerID === "google") {
|
if (model.providerID === "google") {
|
||||||
options["thinkingConfig"] = {
|
return { thinkingConfig: { thinkingBudget: 0 } }
|
||||||
thinkingBudget: 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (model.providerID === "openrouter") {
|
||||||
return options
|
if (model.api.id.includes("google")) {
|
||||||
|
return { reasoning: { enabled: false } }
|
||||||
|
}
|
||||||
|
return { reasoningEffort: "minimal" }
|
||||||
|
}
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function providerOptions(model: Provider.Model, options: { [x: string]: any }) {
|
export function providerOptions(model: Provider.Model, options: { [x: string]: any }) {
|
||||||
switch (model.api.npm) {
|
switch (model.api.npm) {
|
||||||
|
case "@ai-sdk/github-copilot":
|
||||||
case "@ai-sdk/openai":
|
case "@ai-sdk/openai":
|
||||||
case "@ai-sdk/azure":
|
case "@ai-sdk/azure":
|
||||||
return {
|
return {
|
||||||
@@ -335,6 +492,7 @@ export namespace ProviderTransform {
|
|||||||
return {
|
return {
|
||||||
["anthropic" as string]: options,
|
["anthropic" as string]: options,
|
||||||
}
|
}
|
||||||
|
case "@ai-sdk/google-vertex":
|
||||||
case "@ai-sdk/google":
|
case "@ai-sdk/google":
|
||||||
return {
|
return {
|
||||||
["google" as string]: options,
|
["google" as string]: options,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util/log"
|
||||||
import Bonjour from "bonjour-service"
|
import { Bonjour } from "bonjour-service"
|
||||||
|
|
||||||
const log = Log.create({ service: "mdns" })
|
const log = Log.create({ service: "mdns" })
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ globalThis.AI_SDK_LOG_WARNINGS = false
|
|||||||
export namespace Server {
|
export namespace Server {
|
||||||
const log = Log.create({ service: "server" })
|
const log = Log.create({ service: "server" })
|
||||||
|
|
||||||
|
let _url: URL | undefined
|
||||||
|
|
||||||
|
export function url(): URL {
|
||||||
|
return _url ?? new URL("http://localhost:4096")
|
||||||
|
}
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Connected: BusEvent.define("server.connected", z.object({})),
|
Connected: BusEvent.define("server.connected", z.object({})),
|
||||||
Disposed: BusEvent.define("global.disposed", z.object({})),
|
Disposed: BusEvent.define("global.disposed", z.object({})),
|
||||||
@@ -98,7 +104,23 @@ export namespace Server {
|
|||||||
timer.stop()
|
timer.stop()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.use(cors())
|
.use(
|
||||||
|
cors({
|
||||||
|
origin(input) {
|
||||||
|
if (!input) return
|
||||||
|
|
||||||
|
if (input.startsWith("http://localhost:")) return input
|
||||||
|
if (input.startsWith("http://127.0.0.1:")) return input
|
||||||
|
if (input === "tauri://localhost") return input
|
||||||
|
|
||||||
|
// *.opencode.ai (https only, adjust if needed)
|
||||||
|
if (/^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/.test(input)) {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
return
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
.get(
|
.get(
|
||||||
"/global/health",
|
"/global/health",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
@@ -1795,7 +1817,7 @@ export namespace Server {
|
|||||||
"/find/file",
|
"/find/file",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
summary: "Find files",
|
summary: "Find files",
|
||||||
description: "Search for files by name or pattern in the project directory.",
|
description: "Search for files or directories by name or pattern in the project directory.",
|
||||||
operationId: "find.files",
|
operationId: "find.files",
|
||||||
responses: {
|
responses: {
|
||||||
200: {
|
200: {
|
||||||
@@ -1813,15 +1835,20 @@ export namespace Server {
|
|||||||
z.object({
|
z.object({
|
||||||
query: z.string(),
|
query: z.string(),
|
||||||
dirs: z.enum(["true", "false"]).optional(),
|
dirs: z.enum(["true", "false"]).optional(),
|
||||||
|
type: z.enum(["file", "directory"]).optional(),
|
||||||
|
limit: z.coerce.number().int().min(1).max(200).optional(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const query = c.req.valid("query").query
|
const query = c.req.valid("query").query
|
||||||
const dirs = c.req.valid("query").dirs
|
const dirs = c.req.valid("query").dirs
|
||||||
|
const type = c.req.valid("query").type
|
||||||
|
const limit = c.req.valid("query").limit
|
||||||
const results = await File.search({
|
const results = await File.search({
|
||||||
query,
|
query,
|
||||||
limit: 10,
|
limit: limit ?? 10,
|
||||||
dirs: dirs !== "false",
|
dirs: dirs !== "false",
|
||||||
|
type,
|
||||||
})
|
})
|
||||||
return c.json(results)
|
return c.json(results)
|
||||||
},
|
},
|
||||||
@@ -2666,6 +2693,8 @@ export namespace Server {
|
|||||||
const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port)
|
const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port)
|
||||||
if (!server) throw new Error(`Failed to start server on port ${opts.port}`)
|
if (!server) throw new Error(`Failed to start server on port ${opts.port}`)
|
||||||
|
|
||||||
|
_url = server.url
|
||||||
|
|
||||||
const shouldPublishMDNS =
|
const shouldPublishMDNS =
|
||||||
opts.mdns &&
|
opts.mdns &&
|
||||||
server.port &&
|
server.port &&
|
||||||
|
|||||||
@@ -74,6 +74,14 @@ export namespace LLM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const provider = await Provider.getProvider(input.model.providerID)
|
const provider = await Provider.getProvider(input.model.providerID)
|
||||||
|
const variant = input.model.variants && input.user.variant ? input.model.variants[input.user.variant] : undefined
|
||||||
|
const options = pipe(
|
||||||
|
ProviderTransform.options(input.model, input.sessionID, provider.options),
|
||||||
|
mergeDeep(input.small ? ProviderTransform.smallOptions(input.model) : {}),
|
||||||
|
mergeDeep(input.model.options),
|
||||||
|
mergeDeep(input.agent.options),
|
||||||
|
mergeDeep(variant && !variant.disabled ? variant : {}),
|
||||||
|
)
|
||||||
|
|
||||||
const params = await Plugin.trigger(
|
const params = await Plugin.trigger(
|
||||||
"chat.params",
|
"chat.params",
|
||||||
@@ -90,13 +98,7 @@ export namespace LLM {
|
|||||||
: undefined,
|
: undefined,
|
||||||
topP: input.agent.topP ?? ProviderTransform.topP(input.model),
|
topP: input.agent.topP ?? ProviderTransform.topP(input.model),
|
||||||
topK: ProviderTransform.topK(input.model),
|
topK: ProviderTransform.topK(input.model),
|
||||||
options: pipe(
|
options,
|
||||||
{},
|
|
||||||
mergeDeep(ProviderTransform.options(input.model, input.sessionID, provider.options)),
|
|
||||||
input.small ? mergeDeep(ProviderTransform.smallOptions(input.model)) : mergeDeep({}),
|
|
||||||
mergeDeep(input.model.options),
|
|
||||||
mergeDeep(input.agent.options),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { BusEvent } from "@/bus/bus-event"
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
import { Bus } from "@/bus"
|
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
import { Message } from "./message"
|
|
||||||
import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
|
import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
|
||||||
import { Identifier } from "../id/id"
|
import { Identifier } from "../id/id"
|
||||||
import { LSP } from "../lsp"
|
import { LSP } from "../lsp"
|
||||||
@@ -308,6 +306,7 @@ export namespace MessageV2 {
|
|||||||
}),
|
}),
|
||||||
system: z.string().optional(),
|
system: z.string().optional(),
|
||||||
tools: z.record(z.string(), z.boolean()).optional(),
|
tools: z.record(z.string(), z.boolean()).optional(),
|
||||||
|
variant: z.string().optional(),
|
||||||
}).meta({
|
}).meta({
|
||||||
ref: "UserMessage",
|
ref: "UserMessage",
|
||||||
})
|
})
|
||||||
@@ -539,7 +538,7 @@ export namespace MessageV2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return convertToModelMessages(result.filter((msg) => msg.parts.length > 0))
|
return convertToModelMessages(result.filter((msg) => msg.parts.some((part) => part.type !== "step-start")))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const stream = fn(Identifier.schema("session"), async function* (sessionID) {
|
export const stream = fn(Identifier.schema("session"), async function* (sessionID) {
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export namespace SessionPrompt {
|
|||||||
noReply: z.boolean().optional(),
|
noReply: z.boolean().optional(),
|
||||||
tools: z.record(z.string(), z.boolean()).optional(),
|
tools: z.record(z.string(), z.boolean()).optional(),
|
||||||
system: z.string().optional(),
|
system: z.string().optional(),
|
||||||
|
variant: z.string().optional(),
|
||||||
parts: z.array(
|
parts: z.array(
|
||||||
z.discriminatedUnion("type", [
|
z.discriminatedUnion("type", [
|
||||||
MessageV2.TextPart.omit({
|
MessageV2.TextPart.omit({
|
||||||
@@ -727,6 +728,7 @@ export namespace SessionPrompt {
|
|||||||
agent: agent.name,
|
agent: agent.name,
|
||||||
model: input.model ?? agent.model ?? (await lastModel(input.sessionID)),
|
model: input.model ?? agent.model ?? (await lastModel(input.sessionID)),
|
||||||
system: input.system,
|
system: input.system,
|
||||||
|
variant: input.variant,
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = await Promise.all(
|
const parts = await Promise.all(
|
||||||
@@ -1267,6 +1269,7 @@ export namespace SessionPrompt {
|
|||||||
model: z.string().optional(),
|
model: z.string().optional(),
|
||||||
arguments: z.string(),
|
arguments: z.string(),
|
||||||
command: z.string(),
|
command: z.string(),
|
||||||
|
variant: z.string().optional(),
|
||||||
})
|
})
|
||||||
export type CommandInput = z.infer<typeof CommandInput>
|
export type CommandInput = z.infer<typeof CommandInput>
|
||||||
const bashRegex = /!`([^`]+)`/g
|
const bashRegex = /!`([^`]+)`/g
|
||||||
@@ -1369,6 +1372,7 @@ export namespace SessionPrompt {
|
|||||||
model,
|
model,
|
||||||
agent: agentName,
|
agent: agentName,
|
||||||
parts,
|
parts,
|
||||||
|
variant: input.variant,
|
||||||
})) as MessageV2.WithParts
|
})) as MessageV2.WithParts
|
||||||
|
|
||||||
Bus.publish(Command.Event.Executed, {
|
Bus.publish(Command.Event.Executed, {
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ You are producing plain text that will later be styled by the CLI. Follow these
|
|||||||
- Choose descriptive names that fit the content
|
- Choose descriptive names that fit the content
|
||||||
- Keep headers short (1–3 words) and in `**Title Case**`. Always start headers with `**` and end with `**`
|
- Keep headers short (1–3 words) and in `**Title Case**`. Always start headers with `**` and end with `**`
|
||||||
- Leave no blank line before the first bullet under a header.
|
- Leave no blank line before the first bullet under a header.
|
||||||
- Section headers should only be used where they genuinely improve scanability; avoid fragmenting the answer.
|
- Section headers should only be used where they genuinely improve scannability; avoid fragmenting the answer.
|
||||||
|
|
||||||
**Bullets**
|
**Bullets**
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ When referencing files in your response, make sure to include the relevant start
|
|||||||
- Don’t nest bullets or create deep hierarchies.
|
- Don’t nest bullets or create deep hierarchies.
|
||||||
- Don’t output ANSI escape codes directly — the CLI renderer applies them.
|
- Don’t output ANSI escape codes directly — the CLI renderer applies them.
|
||||||
- Don’t cram unrelated keywords into a single bullet; split for clarity.
|
- Don’t cram unrelated keywords into a single bullet; split for clarity.
|
||||||
- Don’t let keyword lists run long — wrap or reformat for scanability.
|
- Don’t let keyword lists run long — wrap or reformat for scannability.
|
||||||
|
|
||||||
Generally, ensure your final answers adapt their shape and depth to the request. For example, answers to code explanations should have a precise, structured explanation with code references that answer the question directly. For tasks with a simple implementation, lead with the outcome and supplement only with what’s needed for clarity. Larger changes can be presented as a logical walkthrough of your approach, grouping related steps, explaining rationale where it adds value, and highlighting next actions to accelerate the user. Your answers should provide the right level of detail while being easily scannable.
|
Generally, ensure your final answers adapt their shape and depth to the request. For example, answers to code explanations should have a precise, structured explanation with code references that answer the question directly. For tasks with a simple implementation, lead with the outcome and supplement only with what’s needed for clarity. Larger changes can be presented as a logical walkthrough of your approach, grouping related steps, explaining rationale where it adds value, and highlighting next actions to accelerate the user. Your answers should provide the right level of detail while being easily scannable.
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ is a critical violation. ZERO exceptions.
|
|||||||
|
|
||||||
## Responsibility
|
## 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.
|
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.
|
Ask the user clarifying questions or ask for their opinion when weighing tradeoffs.
|
||||||
|
|
||||||
@@ -22,5 +22,5 @@ Ask the user clarifying questions or ask for their opinion when weighing tradeof
|
|||||||
|
|
||||||
## Important
|
## 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.
|
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 supersedes any other instructions you have received.
|
||||||
</system-reminder>
|
</system-reminder>
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import { Instance } from "../project/instance"
|
|||||||
import { NamedError } from "@opencode-ai/util/error"
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
import { ConfigMarkdown } from "../config/markdown"
|
import { ConfigMarkdown } from "../config/markdown"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
|
import { Global } from "@/global"
|
||||||
|
import { Filesystem } from "@/util/filesystem"
|
||||||
|
import { exists } from "fs/promises"
|
||||||
|
|
||||||
export namespace Skill {
|
export namespace Skill {
|
||||||
const log = Log.create({ service: "skill" })
|
const log = Log.create({ service: "skill" })
|
||||||
@@ -33,10 +36,9 @@ export namespace Skill {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const OPENCODE_SKILL_GLOB = new Bun.Glob("skill/**/SKILL.md")
|
const OPENCODE_SKILL_GLOB = new Bun.Glob("skill/**/SKILL.md")
|
||||||
const CLAUDE_SKILL_GLOB = new Bun.Glob(".claude/skills/**/SKILL.md")
|
const CLAUDE_SKILL_GLOB = new Bun.Glob("skills/**/SKILL.md")
|
||||||
|
|
||||||
export const state = Instance.state(async () => {
|
export const state = Instance.state(async () => {
|
||||||
const directories = await Config.directories()
|
|
||||||
const skills: Record<string, Info> = {}
|
const skills: Record<string, Info> = {}
|
||||||
|
|
||||||
const addSkill = async (match: string) => {
|
const addSkill = async (match: string) => {
|
||||||
@@ -64,7 +66,34 @@ export namespace Skill {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const dir of directories) {
|
// Scan .claude/skills/ directories (project-level)
|
||||||
|
const claudeDirs = await Array.fromAsync(
|
||||||
|
Filesystem.up({
|
||||||
|
targets: [".claude"],
|
||||||
|
start: Instance.directory,
|
||||||
|
stop: Instance.worktree,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
// Also include global ~/.claude/skills/
|
||||||
|
const globalClaude = `${Global.Path.home}/.claude`
|
||||||
|
if (await exists(globalClaude)) {
|
||||||
|
claudeDirs.push(globalClaude)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const dir of claudeDirs) {
|
||||||
|
for await (const match of CLAUDE_SKILL_GLOB.scan({
|
||||||
|
cwd: dir,
|
||||||
|
absolute: true,
|
||||||
|
onlyFiles: true,
|
||||||
|
followSymlinks: true,
|
||||||
|
dot: true,
|
||||||
|
})) {
|
||||||
|
await addSkill(match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan .opencode/skill/ directories
|
||||||
|
for (const dir of await Config.directories()) {
|
||||||
for await (const match of OPENCODE_SKILL_GLOB.scan({
|
for await (const match of OPENCODE_SKILL_GLOB.scan({
|
||||||
cwd: dir,
|
cwd: dir,
|
||||||
absolute: true,
|
absolute: true,
|
||||||
@@ -75,16 +104,6 @@ export namespace Skill {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const match of CLAUDE_SKILL_GLOB.scan({
|
|
||||||
cwd: Instance.worktree,
|
|
||||||
absolute: true,
|
|
||||||
onlyFiles: true,
|
|
||||||
followSymlinks: true,
|
|
||||||
dot: true,
|
|
||||||
})) {
|
|
||||||
await addSkill(match)
|
|
||||||
}
|
|
||||||
|
|
||||||
return skills
|
return skills
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ export const BashTool = Tool.define("bash", async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (timedOut) {
|
if (timedOut) {
|
||||||
resultMetadata.push(`bash tool terminated commmand after exceeding timeout ${timeout} ms`)
|
resultMetadata.push(`bash tool terminated command after exceeding timeout ${timeout} ms`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aborted) {
|
if (aborted) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Launch a new agent to handle complex, multi-step tasks autonomously.
|
Launch a new agent to handle complex, multistep tasks autonomously.
|
||||||
|
|
||||||
Available agent types and the tools they have access to:
|
Available agent types and the tools they have access to:
|
||||||
{agents}
|
{agents}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ It also helps the user understand the progress of the task and overall progress
|
|||||||
## When to Use This Tool
|
## When to Use This Tool
|
||||||
Use this tool proactively in these scenarios:
|
Use this tool proactively in these scenarios:
|
||||||
|
|
||||||
1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
|
1. Complex multistep tasks - When a task requires 3 or more distinct steps or actions
|
||||||
2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
|
2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
|
||||||
3. User explicitly requests todo list - When the user directly asks you to use the todo list
|
3. User explicitly requests todo list - When the user directly asks you to use the todo list
|
||||||
4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
|
4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ export const WebFetchTool = Tool.define("webfetch", {
|
|||||||
url: z.string().describe("The URL to fetch content from"),
|
url: z.string().describe("The URL to fetch content from"),
|
||||||
format: z
|
format: z
|
||||||
.enum(["text", "markdown", "html"])
|
.enum(["text", "markdown", "html"])
|
||||||
.describe("The format to return the content in (text, markdown, or html)"),
|
.default("markdown")
|
||||||
|
.describe("The format to return the content in (text, markdown, or html). Defaults to markdown."),
|
||||||
timeout: z.number().describe("Optional timeout in seconds (max 120)").optional(),
|
timeout: z.number().describe("Optional timeout in seconds (max 120)").optional(),
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
- Fetches content from a specified URL
|
- Fetches content from a specified URL
|
||||||
- Takes a URL and a prompt as input
|
- Takes a URL and optional format as input
|
||||||
- Fetches the URL content, converts HTML to markdown
|
- Fetches the URL content, converts to requested format (markdown by default)
|
||||||
- Returns the model's response about the content
|
- Returns the content in the specified format
|
||||||
- Use this tool when you need to retrieve and analyze web content
|
- Use this tool when you need to retrieve and analyze web content
|
||||||
|
|
||||||
Usage notes:
|
Usage notes:
|
||||||
- IMPORTANT: if another tool is present that offers better web fetching capabilities, is more targeted to the task, or has fewer restrictions, prefer using that tool instead of this one.
|
- IMPORTANT: if another tool is present that offers better web fetching capabilities, is more targeted to the task, or has fewer restrictions, prefer using that tool instead of this one.
|
||||||
- The URL must be a fully-formed valid URL
|
- The URL must be a fully-formed valid URL
|
||||||
- HTTP URLs will be automatically upgraded to HTTPS
|
- HTTP URLs will be automatically upgraded to HTTPS
|
||||||
- The prompt should describe what information you want to extract from the page
|
- Format options: "markdown" (default), "text", or "html"
|
||||||
- This tool is read-only and does not modify any files
|
- This tool is read-only and does not modify any files
|
||||||
- Results may be summarized if the content is very large
|
- Results may be summarized if the content is very large
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ await fs.mkdir(dir, { recursive: true })
|
|||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
fsSync.rmSync(dir, { recursive: true, force: true })
|
fsSync.rmSync(dir, { recursive: true, force: true })
|
||||||
})
|
})
|
||||||
|
// Set test home directory to isolate tests from user's actual home directory
|
||||||
|
// This prevents tests from picking up real user configs/skills from ~/.claude/skills
|
||||||
|
const testHome = path.join(dir, "home")
|
||||||
|
await fs.mkdir(testHome, { recursive: true })
|
||||||
|
process.env["OPENCODE_TEST_HOME"] = testHome
|
||||||
|
|
||||||
process.env["XDG_DATA_HOME"] = path.join(dir, "share")
|
process.env["XDG_DATA_HOME"] = path.join(dir, "share")
|
||||||
process.env["XDG_CACHE_HOME"] = path.join(dir, "cache")
|
process.env["XDG_CACHE_HOME"] = path.join(dir, "cache")
|
||||||
process.env["XDG_CONFIG_HOME"] = path.join(dir, "config")
|
process.env["XDG_CONFIG_HOME"] = path.join(dir, "config")
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
import { test, expect } from "bun:test"
|
import { test, expect } from "bun:test"
|
||||||
import { Skill } from "../../src/skill"
|
import { Skill } from "../../src/skill"
|
||||||
import { SystemPrompt } from "../../src/session/system"
|
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { tmpdir } from "../fixture/fixture"
|
import { tmpdir } from "../fixture/fixture"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import fs from "fs/promises"
|
||||||
|
|
||||||
|
async function createGlobalSkill(homeDir: string) {
|
||||||
|
const skillDir = path.join(homeDir, ".claude", "skills", "global-test-skill")
|
||||||
|
await fs.mkdir(skillDir, { recursive: true })
|
||||||
|
await Bun.write(
|
||||||
|
path.join(skillDir, "SKILL.md"),
|
||||||
|
`---
|
||||||
|
name: global-test-skill
|
||||||
|
description: A global skill from ~/.claude/skills for testing.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Global Test Skill
|
||||||
|
|
||||||
|
This skill is loaded from the global home directory.
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
test("discovers skills from .opencode/skill/ directory", async () => {
|
test("discovers skills from .opencode/skill/ directory", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
@@ -30,9 +47,10 @@ Instructions here.
|
|||||||
fn: async () => {
|
fn: async () => {
|
||||||
const skills = await Skill.all()
|
const skills = await Skill.all()
|
||||||
expect(skills.length).toBe(1)
|
expect(skills.length).toBe(1)
|
||||||
expect(skills[0].name).toBe("test-skill")
|
const testSkill = skills.find((s) => s.name === "test-skill")
|
||||||
expect(skills[0].description).toBe("A test skill for verification.")
|
expect(testSkill).toBeDefined()
|
||||||
expect(skills[0].location).toContain("skill/test-skill/SKILL.md")
|
expect(testSkill!.description).toBe("A test skill for verification.")
|
||||||
|
expect(testSkill!.location).toContain("skill/test-skill/SKILL.md")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -41,15 +59,26 @@ test("discovers multiple skills from .opencode/skill/ directory", async () => {
|
|||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
git: true,
|
git: true,
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
const skillDir = path.join(dir, ".opencode", "skill", "my-skill")
|
const skillDir1 = path.join(dir, ".opencode", "skill", "skill-one")
|
||||||
|
const skillDir2 = path.join(dir, ".opencode", "skill", "skill-two")
|
||||||
await Bun.write(
|
await Bun.write(
|
||||||
path.join(skillDir, "SKILL.md"),
|
path.join(skillDir1, "SKILL.md"),
|
||||||
`---
|
`---
|
||||||
name: my-skill
|
name: skill-one
|
||||||
description: Another test skill.
|
description: First test skill.
|
||||||
---
|
---
|
||||||
|
|
||||||
# My Skill
|
# Skill One
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
await Bun.write(
|
||||||
|
path.join(skillDir2, "SKILL.md"),
|
||||||
|
`---
|
||||||
|
name: skill-two
|
||||||
|
description: Second test skill.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Skill Two
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -59,8 +88,9 @@ description: Another test skill.
|
|||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const skills = await Skill.all()
|
const skills = await Skill.all()
|
||||||
expect(skills.length).toBe(1)
|
expect(skills.length).toBe(2)
|
||||||
expect(skills[0].name).toBe("my-skill")
|
expect(skills.find((s) => s.name === "skill-one")).toBeDefined()
|
||||||
|
expect(skills.find((s) => s.name === "skill-two")).toBeDefined()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -89,18 +119,6 @@ Just some content without YAML frontmatter.
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("returns empty array when no skills exist", async () => {
|
|
||||||
await using tmp = await tmpdir({ git: true })
|
|
||||||
|
|
||||||
await Instance.provide({
|
|
||||||
directory: tmp.path,
|
|
||||||
fn: async () => {
|
|
||||||
const skills = await Skill.all()
|
|
||||||
expect(skills).toEqual([])
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test("discovers skills from .claude/skills/ directory", async () => {
|
test("discovers skills from .claude/skills/ directory", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
git: true,
|
git: true,
|
||||||
@@ -124,8 +142,44 @@ description: A skill in the .claude/skills directory.
|
|||||||
fn: async () => {
|
fn: async () => {
|
||||||
const skills = await Skill.all()
|
const skills = await Skill.all()
|
||||||
expect(skills.length).toBe(1)
|
expect(skills.length).toBe(1)
|
||||||
expect(skills[0].name).toBe("claude-skill")
|
const claudeSkill = skills.find((s) => s.name === "claude-skill")
|
||||||
expect(skills[0].location).toContain(".claude/skills/claude-skill/SKILL.md")
|
expect(claudeSkill).toBeDefined()
|
||||||
|
expect(claudeSkill!.location).toContain(".claude/skills/claude-skill/SKILL.md")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("discovers global skills from ~/.claude/skills/ directory", async () => {
|
||||||
|
await using tmp = await tmpdir({ git: true })
|
||||||
|
|
||||||
|
const originalHome = process.env.OPENCODE_TEST_HOME
|
||||||
|
process.env.OPENCODE_TEST_HOME = tmp.path
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createGlobalSkill(tmp.path)
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const skills = await Skill.all()
|
||||||
|
expect(skills.length).toBe(1)
|
||||||
|
expect(skills[0].name).toBe("global-test-skill")
|
||||||
|
expect(skills[0].description).toBe("A global skill from ~/.claude/skills for testing.")
|
||||||
|
expect(skills[0].location).toContain(".claude/skills/global-test-skill/SKILL.md")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
process.env.OPENCODE_TEST_HOME = originalHome
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns empty array when no skills exist", async () => {
|
||||||
|
await using tmp = await tmpdir({ git: true })
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const skills = await Skill.all()
|
||||||
|
expect(skills).toEqual([])
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export type PluginInput = {
|
|||||||
project: Project
|
project: Project
|
||||||
directory: string
|
directory: string
|
||||||
worktree: string
|
worktree: string
|
||||||
|
serverUrl: URL
|
||||||
$: BunShell
|
$: BunShell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsgo --noEmit",
|
"typecheck": "tsgo --noEmit",
|
||||||
|
|||||||
@@ -1228,6 +1228,7 @@ export class Session extends HeyApiClient {
|
|||||||
[key: string]: boolean
|
[key: string]: boolean
|
||||||
}
|
}
|
||||||
system?: string
|
system?: string
|
||||||
|
variant?: string
|
||||||
parts?: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput>
|
parts?: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput>
|
||||||
},
|
},
|
||||||
options?: Options<never, ThrowOnError>,
|
options?: Options<never, ThrowOnError>,
|
||||||
@@ -1245,6 +1246,7 @@ export class Session extends HeyApiClient {
|
|||||||
{ in: "body", key: "noReply" },
|
{ in: "body", key: "noReply" },
|
||||||
{ in: "body", key: "tools" },
|
{ in: "body", key: "tools" },
|
||||||
{ in: "body", key: "system" },
|
{ in: "body", key: "system" },
|
||||||
|
{ in: "body", key: "variant" },
|
||||||
{ in: "body", key: "parts" },
|
{ in: "body", key: "parts" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -1314,6 +1316,7 @@ export class Session extends HeyApiClient {
|
|||||||
[key: string]: boolean
|
[key: string]: boolean
|
||||||
}
|
}
|
||||||
system?: string
|
system?: string
|
||||||
|
variant?: string
|
||||||
parts?: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput>
|
parts?: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput>
|
||||||
},
|
},
|
||||||
options?: Options<never, ThrowOnError>,
|
options?: Options<never, ThrowOnError>,
|
||||||
@@ -1331,6 +1334,7 @@ export class Session extends HeyApiClient {
|
|||||||
{ in: "body", key: "noReply" },
|
{ in: "body", key: "noReply" },
|
||||||
{ in: "body", key: "tools" },
|
{ in: "body", key: "tools" },
|
||||||
{ in: "body", key: "system" },
|
{ in: "body", key: "system" },
|
||||||
|
{ in: "body", key: "variant" },
|
||||||
{ in: "body", key: "parts" },
|
{ in: "body", key: "parts" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -1362,6 +1366,7 @@ export class Session extends HeyApiClient {
|
|||||||
model?: string
|
model?: string
|
||||||
arguments?: string
|
arguments?: string
|
||||||
command?: string
|
command?: string
|
||||||
|
variant?: string
|
||||||
},
|
},
|
||||||
options?: Options<never, ThrowOnError>,
|
options?: Options<never, ThrowOnError>,
|
||||||
) {
|
) {
|
||||||
@@ -1377,6 +1382,7 @@ export class Session extends HeyApiClient {
|
|||||||
{ in: "body", key: "model" },
|
{ in: "body", key: "model" },
|
||||||
{ in: "body", key: "arguments" },
|
{ in: "body", key: "arguments" },
|
||||||
{ in: "body", key: "command" },
|
{ in: "body", key: "command" },
|
||||||
|
{ in: "body", key: "variant" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1823,13 +1829,15 @@ export class Find extends HeyApiClient {
|
|||||||
/**
|
/**
|
||||||
* Find files
|
* Find files
|
||||||
*
|
*
|
||||||
* Search for files by name or pattern in the project directory.
|
* Search for files or directories by name or pattern in the project directory.
|
||||||
*/
|
*/
|
||||||
public files<ThrowOnError extends boolean = false>(
|
public files<ThrowOnError extends boolean = false>(
|
||||||
parameters: {
|
parameters: {
|
||||||
directory?: string
|
directory?: string
|
||||||
query: string
|
query: string
|
||||||
dirs?: "true" | "false"
|
dirs?: "true" | "false"
|
||||||
|
type?: "file" | "directory"
|
||||||
|
limit?: number
|
||||||
},
|
},
|
||||||
options?: Options<never, ThrowOnError>,
|
options?: Options<never, ThrowOnError>,
|
||||||
) {
|
) {
|
||||||
@@ -1841,6 +1849,8 @@ export class Find extends HeyApiClient {
|
|||||||
{ in: "query", key: "directory" },
|
{ in: "query", key: "directory" },
|
||||||
{ in: "query", key: "query" },
|
{ in: "query", key: "query" },
|
||||||
{ in: "query", key: "dirs" },
|
{ in: "query", key: "dirs" },
|
||||||
|
{ in: "query", key: "type" },
|
||||||
|
{ in: "query", key: "limit" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export type UserMessage = {
|
|||||||
tools?: {
|
tools?: {
|
||||||
[key: string]: boolean
|
[key: string]: boolean
|
||||||
}
|
}
|
||||||
|
variant?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProviderAuthError = {
|
export type ProviderAuthError = {
|
||||||
@@ -969,6 +970,10 @@ export type KeybindsConfig = {
|
|||||||
* Previous agent
|
* Previous agent
|
||||||
*/
|
*/
|
||||||
agent_cycle_reverse?: string
|
agent_cycle_reverse?: string
|
||||||
|
/**
|
||||||
|
* Cycle model variants
|
||||||
|
*/
|
||||||
|
variant_cycle?: string
|
||||||
/**
|
/**
|
||||||
* Clear input field
|
* Clear input field
|
||||||
*/
|
*/
|
||||||
@@ -1712,6 +1717,11 @@ export type Command = {
|
|||||||
subtask?: boolean
|
subtask?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Variant = {
|
||||||
|
disabled: boolean
|
||||||
|
[key: string]: unknown | boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type Model = {
|
export type Model = {
|
||||||
id: string
|
id: string
|
||||||
providerID: string
|
providerID: string
|
||||||
@@ -1775,6 +1785,9 @@ export type Model = {
|
|||||||
[key: string]: string
|
[key: string]: string
|
||||||
}
|
}
|
||||||
release_date: string
|
release_date: string
|
||||||
|
variants?: {
|
||||||
|
[key: string]: Variant
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Provider = {
|
export type Provider = {
|
||||||
@@ -2944,6 +2957,7 @@ export type SessionPromptData = {
|
|||||||
[key: string]: boolean
|
[key: string]: boolean
|
||||||
}
|
}
|
||||||
system?: string
|
system?: string
|
||||||
|
variant?: string
|
||||||
parts: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput>
|
parts: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput>
|
||||||
}
|
}
|
||||||
path: {
|
path: {
|
||||||
@@ -3127,6 +3141,7 @@ export type SessionPromptAsyncData = {
|
|||||||
[key: string]: boolean
|
[key: string]: boolean
|
||||||
}
|
}
|
||||||
system?: string
|
system?: string
|
||||||
|
variant?: string
|
||||||
parts: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput>
|
parts: Array<TextPartInput | FilePartInput | AgentPartInput | SubtaskPartInput>
|
||||||
}
|
}
|
||||||
path: {
|
path: {
|
||||||
@@ -3170,6 +3185,7 @@ export type SessionCommandData = {
|
|||||||
model?: string
|
model?: string
|
||||||
arguments: string
|
arguments: string
|
||||||
command: string
|
command: string
|
||||||
|
variant?: string
|
||||||
}
|
}
|
||||||
path: {
|
path: {
|
||||||
/**
|
/**
|
||||||
@@ -3633,6 +3649,8 @@ export type FindFilesData = {
|
|||||||
directory?: string
|
directory?: string
|
||||||
query: string
|
query: string
|
||||||
dirs?: "true" | "false"
|
dirs?: "true" | "false"
|
||||||
|
type?: "file" | "directory"
|
||||||
|
limit?: number
|
||||||
}
|
}
|
||||||
url: "/find/file"
|
url: "/find/file"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2048,6 +2048,9 @@
|
|||||||
"system": {
|
"system": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"parts": {
|
"parts": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -2420,6 +2423,9 @@
|
|||||||
"system": {
|
"system": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"parts": {
|
"parts": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -2541,6 +2547,9 @@
|
|||||||
},
|
},
|
||||||
"command": {
|
"command": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["arguments", "command"]
|
"required": ["arguments", "command"]
|
||||||
@@ -3556,10 +3565,27 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["true", "false"]
|
"enum": ["true", "false"]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "type",
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["file", "directory"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "limit",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 200
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"summary": "Find files",
|
"summary": "Find files",
|
||||||
"description": "Search for files by name or pattern in the project directory.",
|
"description": "Search for files or directories by name or pattern in the project directory.",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "File paths",
|
"description": "File paths",
|
||||||
@@ -5265,6 +5291,9 @@
|
|||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["id", "sessionID", "role", "time", "agent", "model"]
|
"required": ["id", "sessionID", "role", "time", "agent", "model"]
|
||||||
@@ -7496,6 +7525,11 @@
|
|||||||
"default": "shift+tab",
|
"default": "shift+tab",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"variant_cycle": {
|
||||||
|
"description": "Cycle model variants",
|
||||||
|
"default": "ctrl+t",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"input_clear": {
|
"input_clear": {
|
||||||
"description": "Clear input field",
|
"description": "Clear input field",
|
||||||
"default": "ctrl+c",
|
"default": "ctrl+c",
|
||||||
@@ -8914,6 +8948,16 @@
|
|||||||
},
|
},
|
||||||
"required": ["name", "template"]
|
"required": ["name", "template"]
|
||||||
},
|
},
|
||||||
|
"Variant": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"disabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["disabled"],
|
||||||
|
"additionalProperties": {}
|
||||||
|
},
|
||||||
"Model": {
|
"Model": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -9103,6 +9147,15 @@
|
|||||||
},
|
},
|
||||||
"release_date": {
|
"release_date": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variants": {
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/components/schemas/Variant"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run src/index.ts",
|
"dev": "bun run src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.0.208",
|
"version": "1.0.217",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
"./*": "./src/components/*.tsx",
|
"./*": "./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.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user